From 00b59f88877e8ed42838ee50e37e3c29f8593ad4 Mon Sep 17 00:00:00 2001 From: Grayson Chen Date: Thu, 14 Jul 2016 13:27:28 +0800 Subject: [PATCH 1/3] version 5.10.7 --- lib/videojs_rails/version.rb | 2 +- readme.md | 4 +- vendor/assets/flash/video-js.swf | Bin 16901 -> 16794 bytes vendor/assets/fonts/VideoJS.eot | Bin 0 -> 5900 bytes vendor/assets/fonts/VideoJS.svg | 102 + vendor/assets/fonts/VideoJS.ttf | Bin 0 -> 5736 bytes vendor/assets/fonts/VideoJS.woff | Bin 0 -> 3704 bytes vendor/assets/fonts/vjs.eot | Bin 4804 -> 0 bytes vendor/assets/fonts/vjs.svg | 28 - vendor/assets/fonts/vjs.ttf | Bin 4640 -> 0 bytes vendor/assets/fonts/vjs.woff | Bin 2508 -> 0 bytes vendor/assets/javascripts/ie8/videojs-ie8.js | 2600 ++ .../assets/javascripts/ie8/videojs-ie8.min.js | 1 + vendor/assets/javascripts/lang/ar.js | 34 + vendor/assets/javascripts/lang/ba.js | 26 + vendor/assets/javascripts/lang/bg.js | 26 + vendor/assets/javascripts/lang/ca.js | 26 + vendor/assets/javascripts/lang/cs.js | 26 + vendor/assets/javascripts/lang/da.js | 26 + vendor/assets/javascripts/lang/de.js | 34 + vendor/assets/javascripts/lang/el.js | 34 + vendor/assets/javascripts/lang/en.js | 37 + vendor/assets/javascripts/lang/es.js | 26 + vendor/assets/javascripts/lang/fa.js | 26 + vendor/assets/javascripts/lang/fi.js | 26 + vendor/assets/javascripts/lang/fr.js | 26 + vendor/assets/javascripts/lang/hr.js | 26 + vendor/assets/javascripts/lang/hu.js | 26 + vendor/assets/javascripts/lang/it.js | 26 + vendor/assets/javascripts/lang/ja.js | 26 + vendor/assets/javascripts/lang/ko.js | 26 + vendor/assets/javascripts/lang/nb.js | 26 + vendor/assets/javascripts/lang/nl.js | 37 + vendor/assets/javascripts/lang/nn.js | 26 + vendor/assets/javascripts/lang/pl.js | 34 + vendor/assets/javascripts/lang/pt-BR.js | 26 + vendor/assets/javascripts/lang/ru.js | 26 + vendor/assets/javascripts/lang/sr.js | 26 + vendor/assets/javascripts/lang/sv.js | 26 + vendor/assets/javascripts/lang/tr.js | 34 + vendor/assets/javascripts/lang/uk.js | 26 + vendor/assets/javascripts/lang/vi.js | 26 + vendor/assets/javascripts/lang/zh-CN.js | 27 + vendor/assets/javascripts/lang/zh-TW.js | 27 + vendor/assets/javascripts/video-js.swf | Bin 16743 -> 16794 bytes vendor/assets/javascripts/video.js.erb | 29002 +++++++++++----- .../assets/javascripts/videojs-contrib-hls.js | 14799 ++++++++ .../videojs-resolution-switcher.js | 361 + vendor/assets/stylesheets/video-js.css.erb | 2022 +- .../videojs-resolution-switcher.css | 37 + 50 files changed, 40661 insertions(+), 9167 deletions(-) create mode 100644 vendor/assets/fonts/VideoJS.eot create mode 100644 vendor/assets/fonts/VideoJS.svg create mode 100644 vendor/assets/fonts/VideoJS.ttf create mode 100644 vendor/assets/fonts/VideoJS.woff delete mode 100644 vendor/assets/fonts/vjs.eot delete mode 100644 vendor/assets/fonts/vjs.svg delete mode 100644 vendor/assets/fonts/vjs.ttf delete mode 100644 vendor/assets/fonts/vjs.woff create mode 100644 vendor/assets/javascripts/ie8/videojs-ie8.js create mode 100644 vendor/assets/javascripts/ie8/videojs-ie8.min.js create mode 100644 vendor/assets/javascripts/lang/ar.js create mode 100644 vendor/assets/javascripts/lang/ba.js create mode 100644 vendor/assets/javascripts/lang/bg.js create mode 100644 vendor/assets/javascripts/lang/ca.js create mode 100644 vendor/assets/javascripts/lang/cs.js create mode 100644 vendor/assets/javascripts/lang/da.js create mode 100644 vendor/assets/javascripts/lang/de.js create mode 100644 vendor/assets/javascripts/lang/el.js create mode 100644 vendor/assets/javascripts/lang/en.js create mode 100644 vendor/assets/javascripts/lang/es.js create mode 100644 vendor/assets/javascripts/lang/fa.js create mode 100644 vendor/assets/javascripts/lang/fi.js create mode 100644 vendor/assets/javascripts/lang/fr.js create mode 100644 vendor/assets/javascripts/lang/hr.js create mode 100644 vendor/assets/javascripts/lang/hu.js create mode 100644 vendor/assets/javascripts/lang/it.js create mode 100644 vendor/assets/javascripts/lang/ja.js create mode 100644 vendor/assets/javascripts/lang/ko.js create mode 100644 vendor/assets/javascripts/lang/nb.js create mode 100644 vendor/assets/javascripts/lang/nl.js create mode 100644 vendor/assets/javascripts/lang/nn.js create mode 100644 vendor/assets/javascripts/lang/pl.js create mode 100644 vendor/assets/javascripts/lang/pt-BR.js create mode 100644 vendor/assets/javascripts/lang/ru.js create mode 100644 vendor/assets/javascripts/lang/sr.js create mode 100644 vendor/assets/javascripts/lang/sv.js create mode 100644 vendor/assets/javascripts/lang/tr.js create mode 100644 vendor/assets/javascripts/lang/uk.js create mode 100644 vendor/assets/javascripts/lang/vi.js create mode 100644 vendor/assets/javascripts/lang/zh-CN.js create mode 100644 vendor/assets/javascripts/lang/zh-TW.js create mode 100644 vendor/assets/javascripts/videojs-contrib-hls.js create mode 100644 vendor/assets/javascripts/videojs-resolution-switcher.js create mode 100644 vendor/assets/stylesheets/videojs-resolution-switcher.css diff --git a/lib/videojs_rails/version.rb b/lib/videojs_rails/version.rb index eb0a825..db89167 100644 --- a/lib/videojs_rails/version.rb +++ b/lib/videojs_rails/version.rb @@ -1,3 +1,3 @@ module VideojsRails - VERSION = '4.12.15' + VERSION = '5.10.7' end diff --git a/readme.md b/readme.md index c4c5af5..154f2c5 100644 --- a/readme.md +++ b/readme.md @@ -35,14 +35,14 @@ And that resource to application.css file And to production.rb add this line ```ruby -config.assets.precompile += %w( video-js.swf vjs.eot vjs.svg vjs.ttf vjs.woff ) +config.assets.precompile += %w( video-js.swf VideoJS.eot VideoJS.svg VideoJS.ttf VideoJS.woff ) ``` In Rails > 4.1 Add this line to config/initializers/assets.rb ```ruby -Rails.application.config.assets.precompile += %w( video-js.swf vjs.eot vjs.svg vjs.ttf vjs.woff ) +Rails.application.config.assets.precompile += %w( video-js.swf VideoJS.eot VideoJS.svg VideoJS.ttf VideoJS.woff ) ``` ## Usage diff --git a/vendor/assets/flash/video-js.swf b/vendor/assets/flash/video-js.swf index cd99f2ee520ad64f64160e79f1f2cc5c93a144ea..a36648f3e345899a1c8d314465deb3f5eea1fef0 100644 GIT binary patch literal 16794 zcmV)gK%~DzS5pk3h5!I~+TFc(e3Zo&H~!2#yZdBQHwgrUfKiqpxP&NHL^Py8BCwc( z<(jfdcG+l>joE~v-YcS50K0;Ug(5bjD5$8Y*t;e~f&~$K@4b+Hzh`EiElK2h-_P&; z=f}sHbIz1A<(xTl&g^cSr&%^>TKrN?o5-|+)B`k4J3sv;2C}u@H=(qkC~IC*V{>o< zsN+ZZ+uB+tj2kz1?%bTY<8uP7v&S8F?6Jp=%RPMD;fIe!i?PA^&264}W1E8`Mokh7 z3cSI()`ph0hCp)`(Vp5sd)x7&Mk%iKbz!D0?X8W1WqsW^Z=<)#+uRl$cUaD0U{qf> z!53(4^0ZCzw6ruf)Om>AxOroP{y^PXb3JpsV||UDpnu}HNJHw=*3j1Ios?G}sP$$Q zHG1b|9g&q6=`UK#Ce*k-GSNv<%k)t1oVq~MxR%yHeS00|G3*p8U2%~d$rj9-b@Jmh4@(w z_1-{9nfAnhri(NUMVhbG)8su&b7(o5m1%ki|57wA`|Um928(8y6KJSU6x*NUokKFS z77~arZ)ozi3e;BC*4ofK+n5{(G!~|SOxhe& z5J@vz1G8Jb!Jw=vb{Dp`2IR7nXL#Gn+B|LTvd&Q5lH}m^4Z#+0wUxEBHne$-S%sx# z#qJr_BXe?d4zpB4%}rrIV^gYwZBT)-hI71PSFIwqG&DD~4QlnyZV0wvr{^JS_B2lS zw)q3~L7NY?L4R_fIj_FnTVEb1BMt)s&1Es1${Xs=N>YWT-XMz61S5Kz>nFGSeBRc4 zPh+FEJ~7Z-SmzH&A{|6?z1s(=dOc0D3_S8`1Fdb68W?CUXb9E?n%f$h+Z)>Ehx<+O zwqd_oysc4XWxdNB;MLw1pxsgI8iL`~l}U}MD4jL{eVTihCxBmTU3k*d0-pLXF*D2% zvz_UI4oV7pgr&DPdBZKsFuEjC0?if8b)NRw{x%`lM73A>Fd}EAN2^P)t(A;JsyQPe z^BFiiBl~GJp--!fa7kEysCdkNJI6FVu&?q?h}KkVH4KHvLYZx{C+I!uh-h`6Ad51h ziy{hWRJS+GYVgiYP(_5>s+;K4Sw)^AhJespJZ*I_b%XoiBM)hHlZVuGR-h3(o?I4a zZ>}$I^)v^`Y!9feYj14@BlEnK&u{UjR0}?(UTCAYxz3whjq}q6t&uG3)v(LdqIF>O z2AG9Gd+|(#TE&Fps+d1Fg{od5tZ8 zk5N5G&BmitHxBdiHbVa_RAQ}e3PyE{0RG!I+j{9%!buqYz67BMyAY!z&6t-_`x(V`~L zFY{vi>w}7#>20k;XG;*`@b9uEIh697a09apF{iOad}FtOF@@4Ty3%7;c2gu?mC37awTGP2(wm zTBMS-hqo3avA2*nk{Eq{v-&e((G3pl_uNhhpW6f>P%`D-daIX|CJFl+6t^oWR|1qP zFQ_)j)7Ivx^NZQ*^$p(CspaJ}^V;hh0w%Xn{g2kCouj(Vj}_1Mx3ss3t1`_rPUYe9 z^lB11l(SVd!D(!2NsN?1wFHqLXlj8laJ2|hXb%w$L3v&=Z+o?Q$Wl$aQ4A3`U^DA& z)j`^MtFVpbF)R{I;iOzP9> z1L`8LYj2c|;bD}=iibQI+}2`4OrI=4(QeqrbcIig5{OwQJOfqch-xP!1t!G>TpTSU_C5V(>dXP%CE@pj6szk0Y zy-w+cH=+?RS&=20+5w%3VIzI>R8cBf$r6~h{C26Q_CWKg(`S;AQ~slDf#S)hpq~n8ve{r)fAS}{95qY0>Y1M&dX(h-`6g3DN z5+u>MeZHW#&8U{5aY#Yc(?-woG`7QiX^FK7snzBc?w;dqZ3XjG5&^ znqQqiHE+h0LL35E**X}S*)^ z*8}U=JUgnqXj(#*IK8kuuOP2HFIn<7t!^5s3MxwT z$`OO9B`2GEBv(zH^YbKE0~$(OnjTeFT|TwcT`^^9vZ_-H zHVBG}DbAQtm|tF9R*|1ySXP!qN48FCDy}=TRcT>)>1lSVYbDK2mK#0=tfv*{pA;{= z!8(de5=e40+~8MOZinES18t-rRC+}25ec%yM`mt7%UGk~NvrIg0N%%-_ZX&DE_H1DrPVs$;dta>l5!`7fp;>mQ27KDpo zotm*raB34~d)tZ?ZlR-=8tszgP>F@MCbpPL5=rP4h1E0N#WTte>}&mVqU#ft%2kuj z{Y~R=J*gPXjlhTZ-`1=q&6`nJURGXOm^Yo2puD0iyZO60{-hfqG|Z@c$t znj$d46IsD&2lwAKx=DJ3CIu2@+57DlZWc9~upav#O*Dz>l0VH|R=EEzqRHS04S|#v z=a*Gi%*0j~9=hMYv2CNq6$Wtsc3n6jjf{CI!8VWRumMwt-^baFPb*1dMXsMu+&&wz%4E9L4QJBRO>ls2CHKpKxi4*S(0`fRpPo0qzEMPGGZ6K=yfEOu-F$8 zDw!R5m=ts{LMMv8>h;4Am92GRB$Ay#s74GSoUwSq~c!GQxcE%PKNGKz7+A%1MxAnl-MnbY4xN&p5vfv<)_QcRJOViRv7BNl%7PDP%7vniQiWseLpmqkl!T zHF}$8xA~LilIm8J7m1}oALn4V>$AeIQdyq5I_OSLR#rvO8~c70<(C~Y`k;6bi%)Nu z2kRP)c`Ht;i+*dg%K!@)J#`A@l^-ynRh_5JN*^1}XCtgZc*kCzJC7Zf{I9CKv8Q{+ zo|8NF*y@~0KD)s{5ZVZH$j-20X$@`sj591}A_}xg)w6)BKW=ubr^Vk;7qr)UXE!t# zH9!~Vl6|!KrD?2h^~|LQSp$|v)OzSe)gEjL1ls&qzGXVNScQbyJT~7-k9w_fErDQz z_(-$QGgMZ6z#Ghhhn=N7>8xl&m-7$WN141s{t0`sh=>1!hS3JI!5fUn-j{mMZikLp zVd%VVam8+Vea9hmn1Z8P7H9e%7K9Qr^x}f?$>L^}_Z^Owy&5)But+XbzP;?AqdIQ3 zv@flCL;d(fd3L4=ZHt!#RgUZPQ1V|k@E^2~-oSstUTok$XxM85p$dVZS8s1^v`{ph zB40AYZxc2IcJ#Ice_VOt_HclRP7Q{lcyrLw0N26Vh~wO775i$T_$MLi)9PU{42a7KP%0u&fKKe=#9@eBu9>B&(FnAp%PNt$N7Y)Q^`)LO-xm|DT$NO47T zOH{>>*aeGSR88-m*_(-)rVZB4_T}aS)!vbWysVaU^|334w^NDxD6pLL%wGYHYhQXjdqPq z$QhS=*x}>jMzJHTM~+TAidpO?YGI67?HM*J8-3)!qsKE_(hw^<=E%_p94or8q`2X1 z!l2_u51YvB_7UuOpa)MHZ_hq~0ULSZ_@8uxaf4e7ZZ(+AV1~it3}!d<1jCYKu;gKO z7H?P`hAq`F(hWAih|4h8Aj5uuVIN}fp$5-1_%MSFH{!Dlc94-c!mv7x#F2)cZ6uB| z5)UyFM;nP_jKo8YM3<2`)=11T62}>dxkln)M&jW{;&>zR2qW=GBk?FB@n|FQ7$fmm zBXNR}c$|?q(MUYrNStIOooFOYHc|@>R%BSG7z0WS>q*9dX$G5a@EJzNOe5}OBcs&F zC^It3jf@Io&?!d7sYb?WM#kwz#=ne=GmMN%Bcsa5s5UZcjEq_%qt3{vH!{2i^BHWm z!TbhmFxZ&}JIi2=25T}{v%vxeYcbf_25U7~&|qx_Yd6>&gUvPAJcG?Q*f|C}*I?%v z?B53akHOA2*aBv-3y>BfU5IoM(jugbkuE{H6zMXg#Yjt#mLgq_bOq9tNLL|UjdTst zwMf@7Bjb9cWy~0IBhpPs(~Kc20B%Nd8AEOXxRn`6w=p9Vqi3FDWCo4QGmXr%jLb$O zv&qQ(kCFLrBNMaA#H=z`BVlHlm{BHXl(`1!JR|dVfU}LvwE*o#<|rc*v&+QnGBLYM z%q|nN%f#$5F}ut=knUv0pu3p;6lM&+2WcJBy-4ekHXv<8+JsbR48ISc));<2z-DH| zZ2^1$@IkJOoFjF?lV3-QSRE9r= z^fb~lNY5fYhx9zs3rH_ABdgFzdKoi*l^G*mXNI#Aa0g%);2TIgk-Cwvlo43U2rOj; zmNEiM8G)sY*oE{aQkgNL&KQ9ejd%;`ZKQXQu+R~^k={kxgY+KKUZnStYK;-oj1eCI z`w;0Pq>qt4LHZO4Q)ZAL`wZ!Gq%V-ZMEVNpYou?GzQw%1V@5XSosBtXV~*LFV>afP zjX7pxj@g)FHs+W81JaL3KOvPF**^o+8QGX`_Af}7Yc}SZjk#uHuGyGtHs+d*d1lud z*}no{mwyAme6um%Y|J+sbIrzFvuUosBmIfA52*(!gtUM|=aDW%T7+~7(q%}Ck(MG| zfpitpHAvSXEkn8i=_aI`k#0p=iL@H&cBDJFG3qYByOHkUM#eh8dy&>7ZA7{cX*1FT zNDm=BjPxkdV@OXRJ&E)*(z8gH`n|A6!(+WrLiGt#fL7rOmoo%R6s z^$Mgbk*?AW{c6B#bi0q++qnIFZg1!I>ve9Qi>Av_bpz6kNH^(5{0hLEk!}HgE8uNN zE0Irk70ieYyeM%=?Fe+20< z^m`oX38eK%Pa-|1+h5S*>@VqYL*wiY!=7r#wxR87NUtMxAax?`K=+fOp=x3L85u5N!tPc)qGp?ELS`$!)keTeiC(#N`S+^2w_A$^V= z`U3Dv-F_Ei_5+gaFS7)DFd6%e(yx(z(Ct6!py}TF8R-|KUy**(?T=FV@4)}i?b`_d zOSkXSlkFj-1(sy{tKhiAl44(q6ik8XwHVGTED#OSRTlf(dI~0r@~bTv0`M9Ox&mHn z!8%j0&J_E1jBQn4;M3%H{FufGGR7&>!ZW{ z8gUSUOw6QjwUrR-eutBkRrZQ>pcJoCbSqh#8dYm1tJ#1nX~@)TkHb?VDup-;;aHSZ z&AjhF(L0T@^e~q8H*Lf73~xs7oirz%+W#VAl3{KE5Y7y1f2tF+EFlo~59VbK+joBd z*Yo1$x!K>mSolN@2KHqCM{8=`4LwM;~CiU_;@#JJ#41HPZljpMP4TIa;_a#u4{)^aBX~rt{pLpYe&w~wWChq z+R>-z+A*gx?by?pHsN%p9rrJ$O+15Z$Dg5VlPbA(LZz;qSjDxxDqWjg&9(d*t`*ei zTA_z)MIK$7Qp>fewYpYZ$F-6=T|23sYt!m=ZMv6hGrYRy_UYQp*<3r>&$ZG9u9Y?D zTKSn=t2m2mvl_W}N)y*kZPK;Vnz?p*v#$Lsz_l}4xK?>K*Q#2%RvqM8O&ixd?Ody! z!?e1&Osk*AH1B+-`Oab5>~opsKaXh*|7O~m|6$r$=QFKw0WQO)3y?K0L>9OZS<6Mp z&Q3DygSDVGaj^XWZBE=GMtgoS^1<3Aj3jd@8qK{7*}TQb<}X2Z&QfIOBC@d$)&2vy zShZCw>k62X3lNCu+Cqe4T)Pm#7}qXBIL5R^2*?;~jZ*^S6AXE!0gf~`P)CA%5d`8Dh|;w3`s&Fl_~cKcwBv?nZtKy9fELY#s93*uBVCGK2#5VQd3>=WS$k zP&Oft=lXpFwELORqsCLd9%Q854~cxMEP7aiM*tpW+BSg4nD)3Vd_scl5eB6l$8(wz(nco%~P-OZp%_b}-2ItC@a zmqClyGpO+f23^|7piG24&jJpiNuY%P4$+K^Y%puK<0BLBF@MSAjmv zpvsT1*RWZSviYeFPcrD+Q|xs#e42G2e};7;f0pe){v7K< z{ycjF`3r0(@)ub*@|V~yr-OoaFzpy`$|B9HEX}__*kpGU@m1%zvt)c?hvA(-!cHkYB(TAz#QZMt&i`1o=h$Qsj&HWymk)i}?xm zkr3x)e996o#JQ9UabC_zbXRa8x+@82S8=g_S4(h>1lLM%odnl&aaNW|YB?uC-aus* z{YFlbyh-FMWc|%t2=W$5-73Lt60DS9l?1CLSR=vh#8F$zl^hpy$Z-jW9G7y)@p6t! z;0g|5UdbWMtN0yQ>D3$}y@o@i*K&yTIu4Ou&mq!f93oxLNu)P$i1bDdk>121(iNOU zdNYSeZ{Z};TRB8}8;3|&auVq(P9j~+A<{J*B3;WN(mOardMAfS@8S^Y-5eslhm%Ox zaT4jh{7y_~J--Y227Wj4jr<yn>h({3nyVdAcXlKRy;I)J-0Cn z^kM_IaSL=}Be&_6iy7O*Z5GSrjNQj=Rtv6(`?<|#Igf4THp2o=TevOG0xl15o81C+ zc#zxTEl`DrxGll5hQdNyqGbtZ4|7|R<#Ns*;kIPUm7G1wZ7G(kIorl<4$HNiJ;rUR zmg_ltoZHeY%Q<_3+tMvJayHz$ohG(|vnOd{w{Z3pP3$(#o~DVd;_Mlk*c#3b;?Gjc zwVXXiEwLXX_2*HNpuNCT_3RgUs%6v|Er-7ZzbsLE8D-;IwRY{_{1xi9fwNbs+a}Hy zVB?wg8cTbPD(>g(b*k9HSqF{$AZML4@>b4v(8!N))+q5SyarO@F$t#@gragI$vv+AvIyl=yd$NPG_h?Vv zz<7-Br73lD_C8JNO$>!gm$MIqtC=!{4YDw87^BDUEOxYoaVJZ+Fnu(eV9_jN*-(pS zZR3d+4Z~{2d2AwL?(^B@aDVNq87s1A@vk!6m=-MorajG~CBmM+4KfJ_{2jpLhd5iz zEm{grApB*!-jNIEjp@uDKdvLUqgd;x95(U~Rx*tFGdp~lHM;LZ-r+mg{SoJtAMqOQ z`xxXwM4}t^&>p{rb>!B3!d>ri_otjG>@#4WbJvHw=nF1_#F{U;?<)${%>i4_sUD1K-u<{*h~4zMpvK4)@QTcXWAvp}Fc7OMLnzSnerfcIIL> zu3x~?>Hd{-B7Org(jn<1+9dXY7-JGWAPzN&5QrR;SfF?K z#+k$gAaYG&A&A3F;zAIIo5V#R#uMRKirvfY%spKj(>*5l&?eeq-*4RaJFoeJuUj@I zEm_k>hs#`->+8}GuT=iYy}Xh6{^BKna;6W9ZN0#Em$8_hS zWZ^ES=19~u`)GStoUS`VC>u&a*)O3`CGAR<^Kf?$bVs*sW82xEJj8)q%J&iS8s9-N zuZ46G`;@C@ut3MG(h+Y76$ux5$_4GZR`*m$>^j{uOI2T>_g%>ax>!k^VwCNvRTT^S zX}nN(v31MR2CBxDmT-BIg?XA3bzwg}FO)qIA$5dHid-!10L3cH6BiqyD!n{#8`E1k3SJ| z_=eZme3wECkEC-?B==nga)e1P)_qHK&r+T6%XQxsy5~w%XS%P_DKfMXpJN0c9Biky zaZFk~O-Y<^kaBA>-REo32Ip#>cesaBf5V3U6JVTtL$cDxnY-1msgPCaHR--*d4*Pz z&fRm^6nc!*?eQ6y!Gz8t*z5&y-J;XHdQbF_v$V`!pIpA*Seb zDoTtg`WF=?(GVY4J8~UAfTO7Jl}}Yjt%)?7NRx?Fn~29mDokXSiPV{hKMHYbNX#sl zZ6Y-$(qJO(CUT~UoMj@7CK8bRB++6bXPZc?i3Clg-bB1p?GCu=o!u_qjv+Qp!}*7+ z<7$%xb2Nikz$C>THK5LygzhK@(V%H}u})a{Bg9K%4ZRaBfK z1=JmivOC~Jxz3S9yyGWLbKR+`5*(j^x=T@sjyFNAQL{>NyacLM63LFoK%6Uy6vrkI zm#B6Q$7)dLNg~y8J&1oxBF%9ri2q0;-El663sk28jwVodtAPhPszEJObs3J6L0zb* zK@OM<*F}mN>^KtCB1IkG7zOHj#bt;C2gWs55<|({Ita^jaLQqD13jV4h5JHF_Jyw7 z7rJF%=#G7%jr&4d_l2I^7kX)5sB2$n_rB1_`$C`Z3w^yW^vk|bXkRFKl@8vESB2v{7F#mQv4{2LUaJmW znqAonm&eoO*wU+SlVdfN>~QBW#|^RTaQ~Y*7WFFn4|BBkGggD+j9z7K_O=}aSa?%d@HcBzx}BINArQYUGfMklFDoumz*?$Nu{QCdm* ztSF1)8jTK2mpV)rfokhgXK5~o^Sjhh!Uf}M?@}kJQlpd9rB0F?)H+4QJM!pEbg7ec z1f7;Hb&#Z9cBz9T^s-AGBvUU_gkGk^=|fXOp_HBbLZ9vn{k|`h(i0lr6DsTpozfG! zpeJ-~Pw1AO(59Zy(>&;h13h3=05%G!#oQ_2~2P!fB5GHkc&UcKlV zojJ98p`oc37~|tQa!McU?y(UD`?EOS_WlNXA-86ko?EjV z=?0|1`0rG0nE2-(gw{jh$ywIJgRfc-SC{$M^QsN-Y?*%puiD667c>7xUbP86AoFkH zRrhh%Qs%#pSKZHD=lSpFRh!|ra{p#twFPb}_iy1<51EW9s5M-8a!$sIxmVVQ6Z=DZVzY);4B)oz1sGFN$Z#gWWrvp#c_phhf^^G}+aq zn!KxNwQ*=+N4*`DJDhxNmoxV+r{#{jJu7rkC;F<6V=%en2gFmaTD#{)G?*%futYVG ztQYoT-Q`RH+qJb8=fu05{O{OX`)6;BY9QH<=*1p<^}C$5wY65k*;Z=|+}#t3@X@qU z7Oq>CrFP_2-mLp>(QD$%*vJKZG8^O6oFUJx`WPoukSnMwXi?CrpiMzT!8ir&3dSp# zpkShcNeU(_n4+LV!Bhp)6iioefPw=R%usNUf`b)2K*1pj4plHy!C?vxSMWduvlKi? z!GjeXp`cU2kqTxjI7-1o6dbMK7zGb?P7=_i=&=gsC^*h}f`GZsBLqCmd8B}cJC71@ zyz^)Qk8mC%;E~Q_1w6_*LBOM(#|e0hbE1I9I*%7{g5rOif)f=yUcpHUo}l1~3g$UA z0Vl&xA(-#v0v0%R0SlcL0gIeg0jD@^0#0=r0v0>t1T1md1w6?aFW@w1f`HSVi2}}W zCJE?vCJQ*znIhoHPKSV{&Qt-*oM{4c!z|)+A1w7q( zfPnu}>pDZhN(HMFtX8l_L638YfVIw{0@gV*1*~@t6VU4%E}+kOpn$WTSpxc<2MO5V zJXpXpog)N1%jp!b(K%AUCTF&Q&CXE*2Aqco*y0>5;MvYG0=7C26)@;@3E1WwD`2}b zN5DDGaRSbD<_b8^d6@ActtRrYi9BK=kDACf6M4); z9ygIEOk}%>JZU0NnaI;7@{EZ*Ya-8?$nz%hf{DCnA}^W9%O>)QiM(neubIf}CemRd zohGuwM7m7m4HMaEBHbpk%S7HZk+)3bZ4-INM0T6VyC$;7MBX!zy(aR$iF{xpADYNV zCi1a~d}1P>n#gA+^0|q8VIp6e$X6!vwTXOVBHxW~=!bTs*=@D?j?)1`xI8RnmW@-dOe z{X;o|iG$?heobQ0%Wx=<@2H$uGEws%Cm!IBqX&32bWzkHT-4(+&R7a0MuGE6{qG_P ztBNFsqH_*SOAa_r2b$TDTk{gn8j`GORWI?5%A}GcZ8Gz`#8C?Fxi)UKO%{{tTvn5` zIW0lFFuS(u;&r*B=56NN%l%V2YA$EKSGa$2M`d0~p5{NXL%jSs>&KKNTl`Zo+F@bG zdJ6a+&f$B{?#QK=X?sVfe~RN26PZB>;_YWBQgWP#P_SE$Vm@3A(=;l0pPP?YW(dng z2+QO6(CwiR-w+Bt6bfw@vFZ5m;KdmH2px+`1l1uTOMfw192ss=f)+>hYEgm~M~7QX zMT=uNS{M{Oh}9iSq=*CyJKa|^64L}~A1{jB*Dz9oC_VGnZ&92PjUorN8`zn_2i>6pDNSE#3&jE@U3K1E9lEZ1>t_vK^4+Y6^cPNjY+dY(>tAPgLuC0 z^iOwKXF&7{I~DaE^(r>iNuy4o`5i2( z=8Pfsqk+{d=9}q}TB4V9dOTnPEK{*)&$oG)$&a^aYThQK38e4<7yns$O6sEvmgYhc*j(4jZ zZ&u&&mZ|Yh>05QZsya2i|87vHMfAmu!A=iX&P3(EB9${yc}8ER%h}Xf#I&++)eUOA zs=ie>sj6yjR^?}<)0%zxzE)XP(@#}UR(bkv$O^e3wS8-DW>d3KQ>W}z&b4<%vX?-B<7tbS_l7B!81ncX9pHR;guN%S_<-recit{2^* z!)Zu|!gq#AwzB9>oh{l)uSHJV09YDubwKH^ntVV&Vf$E96s)Y?WY z+_giB%l$KFha0xkPeP$MBhI#Cs-qbEu6Mnx510%FMv0-(XkiXR6^G#%zSdG}?dYtv z`qt^h%Ck-N)z&%K6m$MM8Unb!fieXT1Pyu(V~VSme@9fQY= zx1N=(SIxtXX2v)>lWEK;|GT^MINY7gQ0T@`=yBohw8Gi6NGC5C!ok^LG~Gm|lXFUc!X?(c!y{(Fe?`;8`GxBL!)` z*MTgFE_e^fQdv+rxMZ;AzZlFeX1)zNHSnF`WbTbRS4Hq4Md>NNO}awv)7|&$7FF-w ztZRgdx=PO$U3khDGx1%ON=J!9^Djc>A{OT2Ax2^PKHYPlptC&>$i9nYU*9F7o2Lpp zFxO((<1Z0Ux;vd(cbHXhSC~r>np|IJ6?C=lLGfLPR64og5}e*3HCV)atxhdneCS0u z3enT{w>=+{J#Bq@22t1*+}T-ctF^9K9qiswYe|M`bkMpH2EICtp+au+BV>^lBk{0EAmSN*}t+vvPa~LQyOzt`A0~wP(w-GepFUA|g}XLz|0}%eRqoov{jbt} z^q-i+c0R>?^gjK#I$QQ({>$llSim%bF1FS6j*jAcnc8L7ZX(x* z$#;p|5GMB!xsg$jOHsAYuDPGzFzeG5&9XT zH%I8liQW>Sw-fz9gno+X2P5>8L_Z{GOGhr<(pw{y4^idAVoT|BP2Yn|xcf=M-G7kz zw#)AeNsmL&Q(e^MktpFlNWy)Taou9gQUx8O{97oypR&ztN0k4+4a#VZe}c>J+<95n zf+zJR){*Uhn9&Gu(+;9g00KQwDh7Q8D9gW1=$!aE5WrKq=4hbmM_AT?8X%RAvXVz} z-95sntY)C^X&lvE>LNhF0N*nx=!z6@-?J##8C~!k3f_n=cpe3B%7Rh;$7v}~P_~`2 zrx?WxPmBB+kv}W)=S2QIHF-g9gO^o4sjJN^c@i$BST9EKvPR~f&*%g4*e~$C$XuW3 z?s;rj9A@$&LpZj9E^LKAAU@M&J?t`mx%iG8dqLOOle+ep*xAK;ubq{fdNyt9!z^ti z1{+0tO*1GuM-v;njaK_2CfO}k>nKnX5Z|lX&V=;c=V|0M?N1{AzZS%P_UrG3w4QI} z+hT{|RAP@@2O*Mo2%~ls;NMEv&V?zMf$^w-{n7+gKXu zs;Q{rO@idQQ^<8#oKW4g;hJWua275#)rv)0P2utBgZA;4^s&btPe6N?S!@>T^!9VkX^JaVc!AH9zfrq&4#%Xt3^CTnizXT)H)yLv=#Kcl1<7f3m+9u`9^8g!c5n+ z?=;ytS?8M7#B)rY1y|d48M%0#XK~gzi@fXx&}&_g`4xdxE_YlZ2)F; zIy-%5)A5Y#3|hS)TEQFYP}A|>CpYXWU6TqDtGF+UVFr!C zS1@vwk;nX!K=EsYa91>@v1kt>v8UXgIDe3uI=2vlUH?f*Hhfz z&8v2C*E8I|i&wqLUC(j;qS5}!l_L|;~mWOYWH$iH}~)5Rqu1xo813CjnKtn+}bz7JNLTUxujwKw`t6s zEb`$(H#}FXtt`fK{atvjUz6wRJiy7waor2q>@^+NeJU!9__ZIR`a`pN0at$QuE^Sc zqqV)s=s`rfKEKgK-eUAxHjoPdU;3PHi(2{_h41TV=jvrl>Bk_^B^ z3BoVg?;X+a)cy54ibndDrHvx^Ys4AG;GMbvJMe{DEjcP|igIWx-;#7biO}B>{b_{$h3L;B^bbUT zE@(K$^iKIjr1ER3{F2cl{Y22C{NFIK*CXZJ++8|Zm(S=T`h>DiVdDH>u@3hiOmmP5 zbdGZ4Y(qW15{u6ure8rl#NxhUzBiO7{WYVvXz4hA#d`6KcAUQvdh*dp2RO~S=tez#}`bj?>tTR@%IfU=NG8L$Wi{EDf@-WzK`LC8Gk2M97a#rKRp`9Z}O%rR1qAJbW0Qn9@-+V_!z7&F?Ow83h$e%6eBKZx^wy6h)> zhHU>2!cvGUXbPqtN>xIABvk8%zlnC zk6#k^Uubc08-!kd$>jI1zhqhNZe3`a4D-}@S^ho!I%~UCv)yL1-TioLGzsa`eW-l8 z56xCQy47CQYOiXw*KDOnd486mhq%b)o9r$l(9k}gz?EzTyNwA~@K<=`R@*#U)oui# zPKtfrWhL(-_Sr3F+~L`!6RgSby$Pg8g$lcw?=3woM_GZlMHAmUdV0ECpDK0lhSW&Q z-G9m(OyF8(-a8p`AJuRnCVnhK8`@F%o|sSxj5Lukjl`VxsL4XBm}iGT z#8~g@?sFJPO9)5^?DwchM`;6Gkv~PB3Ksrs_;*I11i(>*qpWPX0A~*vEmZke(x^{+ ztB+zYJ>kpLEpWE@aZsE(68+zVl_Ch*6XV%_tUO!)M`~!*U{#0jBeuGIr+EsJvZ4-$ znunNR)MT)tKg9hfd|YX7|72G9DKOc&jwRydUo?MAY>klG1ETZ;FnXUQl4Tgv4Ql(Y z9bKLeSe#D5{qN*ZK1B|tP{*snTkyEpf*?3_`u6C)_jFf}K0rK~{mw+mUR`uj)cZuy zFZjea7-W67>jI1Usy~E&VPg6FC;b(n5YG>VP78&Ag#`;|^DmvaMG5vVX2o^YO}}Z% z;+oyDiGGhUingnlg+uU-jW-?pk6A>^&Vhgp5DpV=TD5uEKYM*bv8QG??HUtC@}yWres=euxbC_TLP+Icj}!tN)u^ z9D6h|!e>mWr-*kPFZ^$+cq3Ho`Y2VrFszC-^n<3bDwfg@nk?oo!Jxpdi!J6)!H%Ne zSy{~Af*k_tQj7UxFjDBtEas2FOocXtLK{%%icrY#?+Z2U3nhpjlRVQCdbcNZLMT)b zrQe(^B!hJL7TYGzQz)T$w|)z4NDy?#Q2T5*%W>Y^Fk4L zyr}z;WcE`;W(f1nrbfL)wpH|$abrjfK7>rX>VzP1v86L=!V7rsCLO(-T)=5fGE!P7 zaMOFfFskOFD11>AesL6j38%OYetP8xk&pC}5!by#<6O$=k!ltKyAQbd*@5Q+Uh@HW z7P&vv86ne5WR{6A6S10z?qneoZZQx0_G>7fS;8ZJ${K30l#_ormKrSOS?R*xxPd!E=~js$BD$VFOyoZQ%19J- zx#(3wy>8-Bm(xw$_i=1^CrT*1>+s#q!!KpGiyym+A#Ud~+_@rZX1CMKuH@lR@bAVD zq2LNGL&4QN913E#6bb_DFBH6rheN@ucsLZinukNdYj|`hc(s_|0J&4rpnVkvi-|=N6n|Y?=xL>xK7k1%MKkrzj8{i7sd31M6Ev;t%Wvt zzR){>$e-yhDDmQZ9mwMKa354G&<~>V`mYo1!>8pAdb?PfcK?+Ex#!ju}z9yGvfN)ChLsE4W#EJ7#6G*(%SG;p^1}(y zft+x8c)?rvktg0RPHTsIzUG)lQ(VQx(HP}lMYFh8$cw@>>JWATZsF1`5M}BTU(0(% zs+hqYLNh1GJzls|@H$r<8=OX+zDu}F{_0#X+GH|U!REEJjdzL_Pm*E_$B;MkIr8BB zqsf8IU1BEHl1Gm-jrz)=DxN<4)C$3b}0$&kVivCe?Wu!W~4#VFg4E70XnMbG{l5yn< zn*S;umQ-{|sTcytkaAs=L0nE^xR=%ycMdUL&gJhgmvi|c{&KD&OPGTLqejf~FXjD3 zmP=H#C1$fFyuZlua&^4bocM^`BPa?iIptp>7+fzVBxA9wc=-F0Qa0G`63jSSxZ^i-&J4_EQr5+05-mk1s*bc=2%VmdGvfg(Hd_&hPk4V>b|jm*5@hDYrV zpz6NP90#^o#Enb1h#QxRvzP7pMvrYQ##*W-v6PRK@#9icF-82S&e?K#e1&wB)|y8v ztOhEUT*{-|myMia$qp4uLatA1u~u2dl&eXluI9b;N9}u5Oi5j$V#>d}Qe{jjh6%@% zS8^3oULmiJvsD9JCRcD7Qwk0;ri`73m|)aou(C~@f+^fy^et<9hx;zBIZA*z3Mn%~ zxo`icQ0_&9a*u>U&xArRlB=uswZBmAK5k(2D{yZ()^g$V-^?9#x`>#7oko~51S14( zxQT4e>S)7*I0Bns#t~QY$bJJ}CHLJ|o0hHhGKgWz+-pLP&&4nMm159W>*(J9PTc#B zS4G{eTt#NLnj0y@8#zf~7=71r@%LH6mxYMlup05f7>s%En_?8!;%FA^SMXG(X)vJ6 zcv^|zv5cFR>gGP?s!zNXeQO9@273rWm8BMiGQ8U-V#zf;^+c_IWB5!3rK3 z%vT-0JD20`94=+ztCn3n-|JCi(DQ@tc$Ln>3sL9cy6|~eOn+=3d>+n=iY_5nP|L#c zWKC48X%Ug<#o>6eAS%|hiOAE8H4PDYnz3e_IDgA6<{uX^&tJSafAKhfr-efC5C0W< z`mfNde}#_U7dlP+k&V$kp(}erANPd*?4ehd@J{s?g5J-=A?V%0liDmoyc?Lf0B_=* z(nO^nBy{IC(w&v2dQsRZEKnruyp6=aG9rE%cHYDz(!Y+U9ue`JxyZhcHm*i3ip+tgQ2;dPSc2p#!Py`NB zP|=i2a>qrJT)ayt`aF+-A}U3Spr{~4K*g?rSO8HJv2XzadoS2~H@SCa%atVf`261A z`_IeA*_rLLGqbb1^F44TF+4;_+(JUmp`8)R!n^{dJxZC56RVGaDP40wW-I zZjQI5rFq1#VKZjT$enRYuFpSh*vV&|b=I)F;lqXx9||po24*$2xMmJ*3Jk~@$pTN>OW^Xq&y?yTYl z_spzQv+^VTd27)G8`nh`9T~Mu7xvDr^)(J__WSBuYk?;Z48=P~cflc=TWcEX175d( zWNXvqO}-goibyH8sr9>EExyxGD8@ zZr}J)vMX)sd_tgzc>JzL_sPUYa*2_W5d2Bf2owF@KkqgJF--T>*IBvzbKTQXX~rT% zab@+5Za*hYr7iyYrfK?UpRd8~YKoiaYYn)0O*{xTcREgbS->hMpXjS|H<+9?m$_?O z;>EbFE%gln%jj7x?tH)BH7mZVD!=rUs;b=S?po6nXy)^4^-V1)QG-cVQuvsY8(gz$ zT(y_;Db+M0GtOD$_xl8!DW&e(R)2lVtVmHpDe&FkZt*ou085J#+)Q)(8Z9L-tqbc+ zlUf^V+>!+~I~QFnXmKu-~MrKnoZ|Y5kRME@C534fRd+Ej|72Y4w2?5c^C> znp_PN-7Q{UUBKjl+JHCF*OXsZ=dLUBmEs6#zNXTck;>|8FHew#CGG$eLlc(+6hS{6obJc}2lfwf7Uz1&6dLjqX zfJL@8y2C9?VRn&8@->w=)w)`zd0V(KSfx}7z_%SxD1#ihi6ukX8$dJ zs+DQ9E8sr;)ad;+LKOAvhH2!`%(;ea3J{ZhXp#j!I2r1j-2TLH(YQtssR@AxOOg39zpE*LCs$e(%6*F1c~ZU2F!v+^9%nRfEshY^VzOYg4NHEsf1!3r#RP%C2NWJTDSr z)~lMiA`Hlux@FB0UX9fi<*VY|5w~P?p*u?Iw1lKiD@cr$vbhm-joZc~7G|z% z_4A8^SuIe51toV`Ph4a-2!z zeL0*q)+Tq$44?mUJUZNQFiPDlPU=#a29%|_hOZHvrXuig#4+P)kSO!DxEkX4dM>Q5 zYw^Z&a;&=^9GZ@-!$hWL^o4baHGUYi77U7uH6u2wd_lvk%t%F82a#F>XAMU-@?lMW z?phOYQKM(Up7J%-1!Qir+g}Tv4FQm1yw^Rm%qNb}L~m=OtEtQ(WlEYO1w}YXO)-0?ZaMFQ^Mx!t>jz zTD)Ms)4a{CE&KpaQQo60T%KBmn&IvXo4{UaY_>+qK>s|WoMWA+<0^*bR&ixIn|NE9 zn+%dbg`gMlw>UkXfV)Mn5`{L=1e~b*@+q!{R`3RD7sXZ82PO;4vmnXaS%DJU6^gM% zsn`TMkQ3^syA!HvT}@b)?}d$EsZ#qU3CnRbxzOY)wK<*B-F`oaFi>yN#k5OSu6P`*xE_{RXQ~RI<1_N(CqZNi zye?R1gS9j&(*xW&4kX!-M$@I#2hy%fi!Bu@P7;OVlxovs8;zA3O%}zYBRkF(Rl@yQ zq9`bonwIr^7B1t|DlqT*MqHh06X?~+P{QYPT}qu#UK6>}u`%5G_|nx)JznOWU`eTx zj=o8#GJeC9Wwxkxd>6qW;|fm^OG+*&oA{5MLYH<){E#ehxACLjgp78%I|K5qmYXnZby+Jl+A*nj=r$K>#n`rj8vho$qkpF;)2rF z8hl#TxRWqf-V{*zxB%Y=2{+oXy% z0eLIrEp6qKO3Notc9xVC6;@4j78WJ7f;^g0u`Zk^$ zU6VFCzu^2aCCDqRaDo%(F6$rDyt@{0VMfb(#x#zUCpafnp)d=^=1&?^1SSX)t%VIe z&5bn^ocV=Sz(`4%1$_ed<7VuEMMzA}FE1^!00DfKPb}i26&0N?8rW2>z&Ua9grc${ z6CUeukU;t9^5SCPptyX(1b&5g*P-iAiqsTMoLqLXKEnlGsA*bMdGUnu(y_X`vALxl z3=>(Hkbf~QK0Hf26izHE%P-6?%TE+ztD_|astU_X^2^}OESH?9&I7S(>|Bs9M%BS1 z;6)`hs;sJPEC_kb*hE<;&sALN6z2pGfgIKntrVC@3l|O^C8iI73yd zlA^Mbi!E5^#~qg_1P;bfFkxK5`EmRfTZ`{YYgHZGm+IW$f#Jp|=tOkfh2dg2L7Sih z+*0GGxm(6b+KMYGFRGgC95<<~U)TFiZgjm>%CiaudYs1LdXyiWUz|Rn`?jhY#X6~| zthB78D1RcZu&lgvVE0`*H|r_Nq&UY30YUkY<96@TG>KDuOh^hZKC%0*(M?h#d2}7T zY~bH^3pb0Jjc<*=pN%((>QXSlSz7e>U3im>NFEeYGOnPss(dnty6B|8?Hk)RYF@s( z|9)QH#1<_m^aBI_i!iZGiAJBcBGFPiA48PQ`(|G;H;(tnvhx@~$l6-AQ@2miAn zpe6c2eggFAz6-BIA1UP9xBFJ2vKRW1afLo|hzh)Uyu1~^<6pTC3-PAe`7OBqZEdE;0!V4W-ewtdcrk_~gYo6uD z&RKiz;c@q@(L7u0o9=dGjccmSjgK14=7oVq<7&n=;lP3T+E#e$Hgej~+#2x4*G9FT zep#R@CUAkv0Lh{<%~%P?jW3B4k%)$c_u^3vq^3oJsXQNuzUWANw?wZzVX)$r@5h8AFk1Z?|ScL7u;lF;djkq3N z@VJ`p77hFS%^-cFKQoNz6mYY^4bSa?De4Sx-*hL$1oCuv`Ub!Tj64?y^1&OgueF7` zQ{3TjQZZO8UK)D3W5Z0UJTW%dlfa`hs?zM^JdP_gz(caZ$WL{XxacK{I6IWP@%C$; z1e>fNe?pNZpo-#ni~NTAX-(!b=j5shMa5-C7iNw8dY0;UH-fRn?ASQ@*e~_h#;bQf zEGoX9zwsK7P6dg$S{uUA#1eQMrNo4cL|`)#2}@M*M-ik+uW9hri~utj%D-$3g?A#9 z57X;G)-#=+40Xkyh_$7>G8Y5%iEzo`>JjrN#Hca3i_+7(w-r-^yJ=dBH(>@^S9w`6 z=h!^W?`rne*9I&#?rHT+#q}WU8Mw<$Ua`UJ{H_@o4%A`4@LCs-Pfo87)Pt9>1R8z5 z7B9vd%1b7cxUXo1S7U+*Ld#8@%(o{9I_%2K>TEKrw?ks=Gr{tmH9)bx`tkvH}tHk+zK|WUWa#N19)EpjODcoi<-cd6(=&>5~E@; z27DxFt^NjBzc1i6f`f6lB#EF|IGCcrzKervoxIGM;5v-o%H&e4^-aP74(a7jrEx5~vmmCLtJ zq-!p+#>;Xi`C95dvwB3T1n;@Utql$7T}q4KlUf(FpoQ zYPs#?hCfvl@P~`Jw$6Rwr2>4ir3#KFzGV|j<*Qh1Os&4|lwn=MCjVs> z|Db*JD*nmvxC%@7@dpN9Z)^Zh*kI)B$4C8SptC8J;EouTPSdq?OS&~ZF+JHx(tD=& zPVbj-0#G9}MrGt1Nk%ci_>2h|69HF2xhG>2&Dczh#C$i+cnEO%BQ)a(&3GNijPEh& zz`|q9{4GoWo~8fBjHD;+eM0Z0TJL3A@8w$Wo3-96G$^$6o?s-sVcedc-Z)Ti9HRI4 zG>P`ITIpcN(D>Y8c_$A)g=*#;da8EX;Iz}J(V|Err3vX8J>#^&JO-BM@Kkt-l4LW5yGw93+jdiN)%&cp1I*r#23A!;!r?$*E z$e2=eQ>xC=bk;-H({*!(PJ8K=K052GvrL_ypvPtDw7+g0plf#BnyniK>ed|HI!Lz; z)~!Qy>q)xRp<9RQ)?D2>Otgi>A zdbyrHMNfC==@;th7wPF2>*-VV^h@;gOZD^$omJ`?)w;!{XVmB!wR%RKp5fMeP17^H zdPcpTahaZRxt`IWXEf>=O?rk;&uG>&uFy05dPYFcXwfrTbvj+AGjuvrhZBWfsne@; z`Y)aSTc`ig>D4-&4N{*2Fc;t&fNKHf0bB<#AK-d`8vt$uSOBmPU=hG#fSUl804xPq z2Cy99W`Gp{D*6tKZCd{7+b7sPvnfCy|yqPdxCd`+4F97hExdi~$mU$lltSK`` z&xGkSVfsv%J`<+Tgy}P3`b?NU^L~H_sNU;AYMDm$eh*VU>rudu0p1FD8^Cse#{qT# zJOS_|z*7KE1MCFY1yHN^+YQLC_j?A=9)M>7o&(qm@I1f^0Q&%51h_))w;vFg%1f|v zSP2F0DR54KYnr1|;EDoQ{ayxm1>jYH0{}sQHmJK)rw0KY0(gz;35Q_;N2xvlxE%o8 z4ghWk0Jj5x+X2As0N`VQUmpOh4Y)!d@CKkascv}-@Y{gj0sJn&djRhPd;stvz()Wd z1AGGTDZpm{#{dfT0iOc`J_dXN@Fl=k0AB-q1Mn>{^BvW*L6q4b#_S&eegyal02s^$ z2D5>|Y+x`O7|aF+vq7BMAkOSsJ=?Em1E1MH1ArK_L5$hJem00P8^o9mV$23HX8!^J zqRhTR&o0!nK~C8qr)-c@_OAdSt89=}Hp=QZfZqZB0QeK&FA!Ke;1F&A2F4FC4`4pP z4FC%O76IG@uoPf9zzTp{0B!|X1#la{?EtF*?gUr|umRvMfK32*1KbO6AHV|u4*@*F z^qj{4Zw1)K^o;F*9|zb0@Fc*~0J{L50eBW*FTe``F9N&-@Cv{IfHr_b0EYpN0=xn6 z7Qj0I?*V)O@Dac#0G|SU#`Hm-1O5`=D}b+=KKMJP5BUM;9|3*>_!;0AfM1z@((eF& z0o@MJ0c}HoXKUan0B+JOOEk!@1o~EhRRFhXy5V-fcW9RB%(7ZzmNfv^P-eLa;Cg^- z0q%sd>j1CC?(3lWdVmc8cWHXuM!=f@HUoV(;Cle>1=s>`pJv&LeYQj0;{ZDVp3p3I zsCp9MDXovel4P_bnfmCK^P%8rfSmxl0CodB1F#3+S%BvN_G)_8^O`>B1)%o}K0Nw@I3h+LZeFXSZ%@U_s(k+&hILll(tmrYQ_#EI1fG;)6XIi}FD-FuOhWs}G z-vWFG@IAl}06zl!1mgY$@UNQXH)xynC(wT(&+))>yyZbkEr+QU{I~(eO91~3C+7ly zg$8}ZV!$^6EHPM)Lc6&%5#~z--)*qGfp~?%@}`z(S!qawu@fz~0{#x#wNvX3`9nS; ze(OgGL!cCjDb+CHltEORsnr^3Zh$=0W2G6ozzu<7lejpzG+-y0U?Vwr49KBP0i7fl<`HwLI6qgj z!5|xT`qRC_V)#hyEH1@RYWEDrfLRFv2u97u?hGu3WiIt@8106qsQIPHJ1O;_aK|}A z7XLTyVw&^_Cz>3vQ*zutofj2Z&;P+vx(b>}_-{**jHidUdn8Fx7We-d+y6H-p#3v7 zq>5eh-?U@vnPG`1w0AEPF`P(y6ViwDB_xxa0MhJ7iIxRp^ryslA|<8)l<0O!%-NJ! z22v82LrMG~O00t^Nf<&&;z^VwIViCW1>clQNy;!vQu8QDJDHLm!zoEWg_549Qj&3+ z*^&tJ^vSR!krTAj2|0tDi9YQtM*1Z(OENi;jX-go&B%ar%$8I#keq9_q>+=z85x!y zBsX~^dad&a$snT$$tRs6DH&EoNnSA}Cy$|I_*hC#8Ar*f<0(1qd`eEAK*<>s zDLHczC1*J)8KFFX8E>(12+b zJgwx7t3btZan(F^@wA4wtmSDPPu)EAFmj$pBcrA!3t$-i%gRY0@JLd(hBC$3t8*4#q^s!N zklqGQCPr?DN0UbGfM*jWtKs29$r^Y!QF14|oG4jK;U+}Z(T5;iPalSK1APS2yXd2k zZlsSvx`}S3lPxFEZ7@>tc39Tqlq%YHVAbo|t3+b!$JxCAG_aP0^4fOh2V&OMmh@C7%ItA}H`JI_el#+2=xKUvQOuDFpVFKwk^= zjX>XWReZ^a~}wio)Ln`dy$u1o~5;zr>vF0(A%@2njTs@xnQb zx17tA(%$Audk2;FE-LLk)Yto{uMbdDAEKr{LQQ?lHT4PC)TdlipK(nc;|*FyRiyp$)-b-e z?i9JT0<9Bhy+9iTx=Wyq0&Nm#b68K;G1Sw1270=lfu3$)pr;!d=xG51JuPIHm+#!yeUGt|=^ z4E3~{p`O+-)YF{|^|Y3up4Ktc(|U$_+Q3jxcd@%c`Wx9jkZxl4Lb{o4f%I;6AEfuN z`ysuTJpkzzhWqM1hWqM%hWqLPhWqM4_8`4>0|6MX0{9@ z+wm^eTi?n|)G(XTZOp_Ba~a*vOqu~~$w($frnG9eTPcoBkSjcE5 z>90M7eHSzOH1=J>=uYgrjM41WU08E7qr0(YC8N(^%}Pr5;Hax9eHKTBv*bA(738xQ zN4g@%#>mP3A8a&ssUC%NIM9eS;--mtPOsRB^!neCFiii zaJALR5hy!%HYL~4l%vT06O6u&>_5fm8_51nM&CsCcQg7HvcHGXw~_tl7<~uXf1c5I zk^Oy)zK87ZXY_q!|7Au$K=xl{^h0Dn$mmDN{y|1RM)qH0^b=(N2&11O`>!+l8M6N- zqsNf_w;4DGat|e7X`drw?=t!YGWI?&M9H<_{~7xdD?ViOE3EjK(WG9qr-72b^fb5? zWzjwc!t6BBK(!otj)58+^jrfmo=wj%5EEFsZXkLxyzj4rhp>TI?xyrY1Bp9A;b1k8 zc(CP90b9X;dk93}4)&`e)|(g{8nfKj`cSlk^$ihj@Ee{0gJ>cK!-F_w@FhRgq#RL#lscp5Ga! zf3Wc>23LK>pUm+GwClAtCujVh;MMxXv7?8a?Tj4qbg;IA z&Jbh4L$28x@TwUMaj7s&-i1RB=K(j4Ia<(Tch1omX0Cxuw#r-!nSm-Z4>CC_a~)&` zsmy%H3|5)zAu~i}Zh*{CmAMfzxhk^&GQ(76A!PDYW)WmgR++_+8IBp-LJ(M9Ti#SM znr~;*uWgqUm zUSnf4(}QsRo2T7?#GiCN63?)`=+F$B7MJv#LFz_uaU*h;YJoUg@KF18(Wv~lH$I;spz;c%RIM0c7V#>s(DsvuG=)EZ`V9`Xs*>z-PgHB zBj7qrIL=vo9Pmtb^5Bq^IAn*rD9EPt#`X7gE~O;eW!|Z=pz{PAKsUhvXM=n7^v+7n zQ~K%F*@>#e>O{{|telKbWKMWUj=|uqW{FD&9?oqmKE&v3%Msr4pz|`UwNRJ<_D|bb zLgFa+@_0U(=Pc*tIDdRhQ3DoPalD_5!93gV(4AL!JYyvhP{eGisH+vxtO&m%rYXX! zh!#a$8AaF$KzO`ainu}%S1DqCxY559@oz=^M-g+V7*XWrhPzx7rmhWB^TJfCBBqbE z*x;$rcEmCLU~dy4;KQz?#BmK3Icl2;xg|2kY;f6h+$3|F?NZ3imN|oM66EH}oY6K4 za;r6wGuehiZjH?8Haq0*mpQX79dZxIoW*8_+>LUSINKkDI3ARB@wTrZ_mIq4ZSO+v zUfD9i7KGd!nM<_of!qR_OR_x%xm#q;X4?e0g)*0HyA^VaWG=;aBjgs#T&nF~kXtHq zX|^WFJuGMMVXKDRGFg{yD}mf{nd^yr*m1MWW!O%K+zOfNWg7&!JLM?7ZLnb+*U7p* zczW27>T6?|!ue6x5$fCA9-7r2n%f>)*dAKZ9$M2L+T0#`s6Di!J+!Ai^iq51wf4~K z?V-2ZL!Y#VzG)Br+8&}Ep}3AvDs=7$4eSUF=?I=#=;mBwF@dayW^y8N~xrb-M4(QkTer>GLKY7b$ePfD6>*8fl*T@U( z)YWABjm8c(&Gu=h60hww+I9G=Z96-)n`yfbOAb1RQrr4irk(IGu`TXY2G11RmEDYd zm94JRlCQ9pbegLbtvIG+x-D0gRG;Q~oV7V`q|DZv613vBqhSAsoX*YhTA4F*b9_Mdvv6~KROaHi zIWCYbOByNsLX04AN3MIYU9{R35#5zK~Izs1mgq$5A zS4U_@N9cx*(DIJZ=8n*l9ig_4&_^AipF2X(0eoyCo*QY>?ee6J>mUGiNet=|!DBlf z(~8$>)K2aL!?YPh54;0z)7rx4BVJ&WakL>YS{ok!$>HO_CD?|?enR-zZ$pi>K#vr3 z`&Q-l7f|&3sWD{>d0ARa*&<%nBc^OIFH4UpTguCN;>lx%bvW1YvoZtMl`Pv-pW|s` z&Kvjv){DV&V_-1PO{&d#)syq8*K2vz8vyPC$bkPYBANV;whx<75KF%t4!1bGXcd*JQ;I_@YPq4}-;d0HqPvRS; z52Xg9i9c;1*B7|Zk`x!_i)g0zG$_uDJcUkRu*m-6DO9Z&A7R9MX0YOn@n;b4>HN8J zIzCsRVLy}x4(vG-G?#_T%L(RHY}AgoJ|@@K2Rip>z|{$`zNg@_&%95u%BPuQ3G+V9 zDt9u+GUnZhyq{>$jHU!s`M{iUTJWHE1QZUS)M(=Kju?yk3f7mZ7R2Dci!_)c5gKNr z3?G2kS>-O~xS4r(vC7@dv66Xrv&v_Hpx$R#Wd0$|a`kVdf38$~NYBlzH25 z(F0=?meWaLhD`4{P&^3NW`m_2WEHQm;@3b+2eI>DW1I$0>|maq9I+F4n$#Kqi~y!K z&n7KUt5;aYTgYt7kXmz_b2IL{T8phOIJasTDZa*BV;Zu%*5tWcD~_j-296xGhtlBH zY9o{!r^(SnvdKGy)R>`#1?nwOdC<-_9H>v&(;aR@MXZdx4ns zL4&bk3PV%_G4$L{Lm#py!LS=^4EA&GwzK06Z|r_}V^jk%{D4lw!?@brcGJchBOlpR zqxX2bBNQ2jkRb-98CJqZuee9^+^bc`m(uLnY&0EWC-#tQi#EhgC1w(95*s8oN^Fu? zm)I<^MdCP#<0ZCAoFH+c#7Pp{Bu&;u9qvAhBKIY>5X-oFnldi3dwOMB4j~+@8kq6nhVjFSMs~e388; z#~0f(IG$?n#qlL_U6)E+A#tU|RT5WA?6UXfxW?Xx<63)Pj_d519J}o&aO|=7<9M1q zi({|7Kgad<6FI)jK7ixP?RJhE?AaVQ+6QvnWY6K)XCK6IvwbkfSJ;Pe?6;r9alr22 zxWzt{<5qhv$J6b@IG$n8<9Me1WR7RqhjV1n5T&A6fs{B*DK-%Mck-}1&Uaxh((H6tcaTwu|yF|6|qba%N236B33A3 zr6O)o#I1^0rHI=Ual0b!P{e9QtWm_Bidd_Nb&6Q8hz*LkOA#9tu}Kk|6>+yB?oq_O zirAuv`xJ4%A|6o0gNk@a5f3Zk5k)+zh{qJMRT0}1v0V|5D`JNto>0V-ig-#9Pb*@l zB6cZaw<4ZV#2!UFtBB_mu~!k#E8+!3>{G;xirBA+mlW}`B3@C%tBN?Fh@c|c6md`y zhZOOeA`UC!h$4V7scvlhcDdK%ae4vOA74eZGK32phiuhC! zpDE&)B0g8d7mD~&5nn0dYejsch;J3~og%(h#1D%2Q4v2W;%7ztqKID=@tY!kSHvHR z_)`&oDWY8w9f}AEVz%`#2Ek5>xkNhf5(&4(iu*Kii!^3>&xP`#k!ydh5pMIFSj@e@ zAm}NmPW0@AOaC~G6pHNAfX76#PXQjQvI(Ad0Z)u%Uk5xXl0634$+Hz1<1>hNbkH-} zbH4_ao<=)$KA(DxHsL@YEZP6-};3HU20W&A*|DLC*_T zZkN40=YYiCj3>NHv}7A6yIn- zA|qWs5Z5rxV6lD(Z;Pzi+qA=3v$tzUu;)w%P2GtFau2nP74j61a4~UAA zB+nC2a(+~a&GRIbOi(3HX~^=^8VzQ7M^$X&QkXe@n?`ZEPV1}Ku8rTW5y3;ugk0`1 z6GlqZgrcPFAiR&6EfJ&8Q^}B{JO-U<*2d81=>d9(f2|;S2Hg_(JH^MvVR&CC!3B;g zp#|W+URP_xY_n&l`T$XpstSrzX#lH7df)&gsTPzF(ohKfOvmZBOY}2!?l%t$4+Rdl z!TsF0VLd!mY7B{1LfUE!2O)d7#t;A=N45#Y9uyj_-mOI}Yb2!W>}*5vGa7a6iK*s| zAq`hQi!q3k4Q7k=)vyFQy|7TS&uLkyw<3St$VSv-DGGtpSpMJFFiNaplvu;4m^F;* zyoOORYZw)~23wNUaIG#?tdF5RpP)O1hKC!OC9VkH)_kMwu9phclbcqf@5fWuS}F6-KP=C$mtlXO2wrYRG;SA!P-18Y8WyE zj?ajxdLp{&OpG1HVHMxkP;ypywS4g-VzXzfn9Kg-##~G?=5odu{D+g_Oc37wTn)&+ z!My6jEUPz&pz<(y5-8$Vd}2nnv>jp>1V#@}nv7TriF!LMXxN$C&3! z=DjFb{VDZ)&b+0;ijwgq#Ct)If7!9u4H=(e@LmkFofnSSTm<8eVw%z94d&VU}TT=SMjqVK)Zmr`@NbAZO4&a$`&~-26jEQ!z3dK08 zM#j1;kge0?!o$n`hbycM^X~@!t5AmNd?DV0T=jy8Y zTcze}7*tuf!P&_cD5yne0j4nMNLxw@wk>Zxg4TUS@SwLGH$E6wx%Uc~udeHSo5XzX zuJf&t^Le^ft(H~O!fUODoHt@x)i7CoxY7lcmqjXFP+*KqSgh;OFT634E@k?h`@ru^wd__Y|1~hm|z@?!7 z+#^}DU)A77GZn76Cx?I2lml+M`c-ZITrk3^;*-r3cCh#WTvnkq7Oc>+4mpDwZu^!l zgI#jm!7ll4gSFx^lB1U)4Rw0giu-xRjII?A@QRsTD<0$(vrr&XRQ$It;N&0T7Jg+n zH4pQetGW*M2p{ZU8kqA)ygs%bX>+`%6}M^dY)b{pZVPAW+Tw#6op%`H^>$O56)(b$ z545(ma0_ldau9U%Z=Bc;I^v(Y@X|6Q8C`_Te+ZWtI1C4Jef|f}l^)=;pV9H3$@#*K zE{MG`jK)bX`rq#iPs5#|PbhS2DD*VHGt8#&U^a-m(i|G|mYz%Ti&KA){$X|NntYKC zS1i5ah@}@@f7pE&MMzvj-AZ=2CICNId(Je zo2>FJ=Geo$Z?Vd^nd3R;eVbLj!yL~u?>nsWUFO)wyzjEg_n2cp^S;L_-)D}OnfHBG z`2lmh%Df-2$`6?%$h;r2%8!`iAoG62DnDk9*O>QXR{05Y9AVy1Smme8@jCN<$|^r& zjyIY2Ggf(wIo@X8W4M{FrDzR_QD56uqAky(Xw2x{JS!>K%yoSI;BhLBYT-|A7Fd4* zG@efh)wt8R8=En7B-?1#MRDE>q3j+Sxq;qG4OARNk$A%)8Z0N?eN_Ae(@w@`GVhaM z6Hik4o1zb>{Nd%62vcpy)O{4UgqgeGHk|%`ihgk@HrPS4Qv0D@V2CW$C@8oFJWH|8 zbLHBxHVy3rhIxSZ8jrp9QGW6!@RN5R^}H?~3|N5gQeFZZ62>`5!Z#&T|P69IZtOTt2Jtepw&X=D)(bJ+~U z33k$CQ5y8@62laWFil7*g+Hx#&^G65+>t#>)h&h>zQtAh7+(P{X`uHx zEZE8m5=E<^=St_>yxlflJV_K!7rz+`dS0NOceJc6DQP&9)WI9tTBVDi$8RTzX@U*U zVNrh#-2mn5g+#aW_4N?z6ONa+6t$1@S|hqx$nA*9JrS{*7tv~-jO6!Y{;5d*0Op^L z;F9l{%aTaNP$e#L!;3jl9OunCfJ@j;P-l?c!?XB%2hq=Sh{A>RS?*nfo|kAc|Ct3Zd`1=? zqVPPyz+d$9WH0CVVxfEdNd<}=Y{0~AU_V`%G5`h|7@1;EcnZGofxwBa=W#iO8J_1e z6bofWjXXVNm@tX|VG%Ig3*6E#5yPdNz*%^i`yTPL#{Gh?Ht+M)mLmCzL` zqRPDSpnH_BhNE3~aC4AO~2_ zoAv5cA6*FyyvXe&UoP@=DW~@|qD<;QM4#_P^cfiv@cTTrvp>qM?Zu&A!nK)i;3l|N z`jNdf%lU!E)t1sPYO*Zv4jK+M9+q(^XtrH8+pd~zSIua2vnORU$y64c5waPJG#jJ! z8RVF+eW;CgmLjU3ekCIEGUxLuzLj<=)7ULdV>^{UPZb<=&tr8D&qv^e-=jQMi%bS9 z9;f3UhpWkU-XQ4uSc`4QW2`W^^Ak`5&CDLmS$TyTK^yl{bx55MB_hDG`}=AhaIFrFfqNGszotIMzd_ox}$Sq8aV#Vlq|{^l$u_)SQqp>Mc20;R+gV|V)TxXyl^g~8G?l!phj@U(}Q`o zG`vze-vLiK9w=K8Cb<1P$28C9+7ZX^nz~rx&*uKnj(@9s5pI=FghJ1SLO_M(3irHU zH5@Hku0g89^jqOLv3-rdtF(u}(SXwTp>qjFdl-29!lI=lyie1JgaNNX{0#aQor`fN z&DkP;Dkr@#dR+YRUnid!_Timbvl%sf0!SOd=Rx%sn&(Rm?+0>>=Y&q*=06AJCC`UP zh0Mz$BNoKHrDqRKN#`zIo;G2|_)quY1n6?ejLIc;-dPApw7lDR}rKMx;OIEnW~y{GI;>w2dZ@CFK9cl(x8NAK%$bZ8zd{ z@_czYGQ&s5n_+MC3@?Ni^|#hy%NCRD7X421Y-7$FfwM#^w8>|LN5akAX?U)sjunEM zN6T5%^q%JM5xf_U;4Gm)PqXah`bvw+&!eudHQN^4Sev3YR(p73;cr|!!W#>J;~LV` zPtox=uCop5r|9?_*Et6DQ*``|>s*8SDLVef^%{fvDLQV}YYpnB=xVc?`DQi4W^E0H z%!~dC^=S_kwud&ghjKbXD?37ec7#$wq1LGF`(}6Bw+-Gp;y84^%Wq@{sSye;=jY=- z+9sS?>WSQLUO|(2RVnHvCS4T<{A}V%@d~Q^)riXB)i|4Cv(75t&pXSA>LEU7A3RQE zKX`V|GqgoB`dXJZZ**yMm@h^=(~j`h9$dC7zKOhc+eZ@jml@f&9E4`*3~(htlvDbKlB$vNhBDDGu{y)NA`KxS|-0vG2mU4DYYs#k>oT zfe4PNcj2{6z6;;xQ}Rps_jtvA2d*gwqqwGgk5j$N-{ga_!LJkpsX@=raP(RQ;rUL( zV+CIl;pfzQyax~OcTiqS$ostP6fB#=!q2l`)w?R_`BIP!$l1(pOcGBoKHdkMMXrIb zyXjy@wm$?7aLGC58=&|jB}m8hnEg1My#ceIaAx}AfcSLc_eMOGV;)VPMg;R0&hS~M z{IN*B9rK?@^1ovKi%9-=%zqil|B3mpBKbcs|25CUQwd{G-$W|E!^&?t$M{oj&$rQs z{?eX|Ie7g_p0%Jg@=j3=nS(b-V_}?Aa{DFvD>g>{XZM` zH!iOerM!AZjl|7ipfU$&`aAd9;u!k_o|y)n$G(0R=Sv8b#~e}aEPE0=rBiFlzgl`Dx6+q;0>*Z)sS}DwqZSvJEYbCC*AH^V~wH4=$g&UMzcYjeQEqs z-)S0KB^F|JVp(|j@aygi`MPU8B4?Bh)*al?dK8b`mEwJK2#33$e;4e+HZ1-xt`B}G zyN2OO(9RE$6Y$J{(QxCo!Qi-cBNBpU9lY5fXr}JXUkJgi;cv%ZyV!DwPn3bm?j&A0 z@oWaxU_4)3OOpGLu<`t<#*z;L+>c^E^yy@?b`ou`_*sG>mpynO<7+XDy1nC93O=J9 zKR+)q@YVea7goGOiTUv2_?@~qUc=PQ>^FHcOUU$ohQnOTA`$y}41-6dF*mc#{APAL zgPWPXm!0CpY%z?yn8}OT7U7|y?rlqc=2lRKb_ysH+i@%wLWAQ6kI2ON>MaSY7GQ4@?&2A~)>=rTknc1-nvx~#o<(R#R zGoPaFefVn+k6)~72@8kFZ)V~y1&pc?`BD}g6kisTTOLuwYUJqVNPZ3GS48r6VSZ&K zzZUbiu&5CEt&z&xv2qo|_~$v0&+*>DxLC6VZ!!kC5>LyUF#)I6=Dm#top&>0Lv4qH z>bJ3QQ2jP0gX*_2&mZ!lcsq*()o){+#*Dca-oYY4^;@uhHG01_Vos=E&F%d@DwQXK z>Z)N5HWa4MS-b|>Si{2S;$3WI${D=Jt&A^F1f!voXLIzSxS4ehp4-mEp*FCTJRuqx z92gj$HOIRFle>`HJ7dQKuGjEYd4&SQ?5qKHPLiS)(UveLDlTy+lY#eJnQfq8A*Q?V zvYooYgm>}@Pwq0|bn&kO9B=8+b1mQGmx`YJ{2S<9%iV+Up^I3XT>72NHe5<&2#Kj# z{omxVj)i0HoV_j^e5#x`9CKG9kBPZQ@x){9e1NE!dsKaNB`~m_n^Cb`<=HXz&Mj^O zCU>#0EyM)g)wqB~;C(}sXIzE*;VxXD`5|tmtC$G9uVOOrzDUSb&6wp~#JanFER@X_ zs%8sWcY*h%vYBKm3!Hh^z&m<>(QL75wpcaOqAoLrZsYSy=xVx-^4rZurctT6g)A%0 zjyHT6XJ5#$OoX@>G8y~6Pu_+XGS8n{O0M(^fAJ=sb}cnktWcI>D4zdh&3O-O-=r>e zAlXd}(oRMJ%=v=V_5-7Hmtz;}?m!I%GV4m2eVV zXPcvM^U@|{EPfG-j?-*r(Xn{$;5iD%;y0pYtzez4NKO$Qi|4(fV)4g24Mi-TPZhqc zEoU+ozl_O&WQ%n{&oU-r@q7poi|@n>=Q5fh{Ote>N7IBWzK0nNMiaaq^K47_Pf~dM z142ezfjfBd5*X?xW<3Sw8FB=2yN@0`#y`hwX1rv!L3ESZIXsKO$MBbd*@pk+%fPBoh>Z+|%0r=57+REC?)J;Ty$nB0 ze2)K8$#6z20iXe2{;P__NDdHaX@ZwAh9~v^uUoAr78@g;9Td}C&htAr_q#qq`(!J5^ zj6~%BR!z1K2%lu7GFcgs-~Yc_=PIze6;al?%Al+h1M2Nz>nw>0uNTII*H4cLuMdg| zulJ4#uPYnZxsB^!f-%yRLvt%^rKPqV^Q>0rULVG4dK9i zVrHgy0rIn+a diff --git a/vendor/assets/fonts/VideoJS.eot b/vendor/assets/fonts/VideoJS.eot new file mode 100644 index 0000000000000000000000000000000000000000..cbdd2a20c0b0c6c78b226a30c034f4fa2d40d0a4 GIT binary patch literal 5900 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 CycAmi literal 0 HcmV?d00001 diff --git a/vendor/assets/fonts/VideoJS.svg b/vendor/assets/fonts/VideoJS.svg new file mode 100644 index 0000000..49af73a --- /dev/null +++ b/vendor/assets/fonts/VideoJS.svg @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/assets/fonts/VideoJS.ttf b/vendor/assets/fonts/VideoJS.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e23d02a49df665819a9546b58e5bcf8ba2717532 GIT binary patch literal 5736 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 literal 0 HcmV?d00001 diff --git a/vendor/assets/fonts/VideoJS.woff b/vendor/assets/fonts/VideoJS.woff new file mode 100644 index 0000000000000000000000000000000000000000..dc9d850472d8c707d00c01df7af4d624664e27db GIT binary patch literal 3704 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 - - -Generated by IcoMoon - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/vendor/assets/fonts/vjs.ttf b/vendor/assets/fonts/vjs.ttf deleted file mode 100644 index 682a9b21ef23161d9f1ce0087c4385a8b31b3e26..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4640 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 diff --git a/vendor/assets/javascripts/ie8/videojs-ie8.js b/vendor/assets/javascripts/ie8/videojs-ie8.js new file mode 100644 index 0000000..8a660b9 --- /dev/null +++ b/vendor/assets/javascripts/ie8/videojs-ie8.js @@ -0,0 +1,2600 @@ +/** + * HTML5 Element Shim for IE8 + * + * **THIS CODE MUST BE LOADED IN THE OF THE DOCUMENT** + * + * Video.js uses the video tag as an embed code, even in IE8 which + * doesn't have support for HTML5 video. The following code is needed + * to make it possible to use the video tag. Otherwise IE8 ignores everything + * inside the video tag. + */ +if (typeof window.HTMLVideoElement === 'undefined') { + document.createElement('video'); + document.createElement('audio'); + document.createElement('track'); +} + +/*! + * https://github.com/es-shims/es5-shim + * @license es5-shim Copyright 2009-2015 by contributors, MIT License + * see https://github.com/es-shims/es5-shim/blob/master/LICENSE + */ + +// vim: ts=4 sts=4 sw=4 expandtab + +// Add semicolon to prevent IIFE from being passed as argument to concatenated code. +; + +// UMD (Universal Module Definition) +// see https://github.com/umdjs/umd/blob/master/templates/returnExports.js +(function (root, factory) { + 'use strict'; + + /* global define, exports, module */ + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(factory); + } else if (typeof exports === 'object') { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like enviroments that support module.exports, + // like Node. + module.exports = factory(); + } else { + // Browser globals (root is window) + root.returnExports = factory(); + } +}(this, function () { + +/** + * Brings an environment as close to ECMAScript 5 compliance + * as is possible with the facilities of erstwhile engines. + * + * Annotated ES5: http://es5.github.com/ (specific links below) + * ES5 Spec: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf + * Required reading: http://javascriptweblog.wordpress.com/2011/12/05/extending-javascript-natives/ + */ + +// Shortcut to an often accessed properties, in order to avoid multiple +// dereference that costs universally. This also holds a reference to known-good +// functions. +var $Array = Array; +var ArrayPrototype = $Array.prototype; +var $Object = Object; +var ObjectPrototype = $Object.prototype; +var FunctionPrototype = Function.prototype; +var $String = String; +var StringPrototype = $String.prototype; +var $Number = Number; +var NumberPrototype = $Number.prototype; +var array_slice = ArrayPrototype.slice; +var array_splice = ArrayPrototype.splice; +var array_push = ArrayPrototype.push; +var array_unshift = ArrayPrototype.unshift; +var array_concat = ArrayPrototype.concat; +var call = FunctionPrototype.call; +var apply = FunctionPrototype.apply; +var max = Math.max; +var min = Math.min; + +// Having a toString local variable name breaks in Opera so use to_string. +var to_string = ObjectPrototype.toString; + +var hasToStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol'; +var isCallable; /* inlined from https://npmjs.com/is-callable */ var fnToStr = Function.prototype.toString, tryFunctionObject = function tryFunctionObject(value) { try { fnToStr.call(value); return true; } catch (e) { return false; } }, fnClass = '[object Function]', genClass = '[object GeneratorFunction]'; isCallable = function isCallable(value) { if (typeof value !== 'function') { return false; } if (hasToStringTag) { return tryFunctionObject(value); } var strClass = to_string.call(value); return strClass === fnClass || strClass === genClass; }; +var isRegex; /* inlined from https://npmjs.com/is-regex */ var regexExec = RegExp.prototype.exec, tryRegexExec = function tryRegexExec(value) { try { regexExec.call(value); return true; } catch (e) { return false; } }, regexClass = '[object RegExp]'; isRegex = function isRegex(value) { if (typeof value !== 'object') { return false; } return hasToStringTag ? tryRegexExec(value) : to_string.call(value) === regexClass; }; +var isString; /* inlined from https://npmjs.com/is-string */ var strValue = String.prototype.valueOf, tryStringObject = function tryStringObject(value) { try { strValue.call(value); return true; } catch (e) { return false; } }, stringClass = '[object String]'; isString = function isString(value) { if (typeof value === 'string') { return true; } if (typeof value !== 'object') { return false; } return hasToStringTag ? tryStringObject(value) : to_string.call(value) === stringClass; }; + +/* inlined from http://npmjs.com/define-properties */ +var supportsDescriptors = $Object.defineProperty && (function () { + try { + var obj = {}; + $Object.defineProperty(obj, 'x', { enumerable: false, value: obj }); + for (var _ in obj) { return false; } + return obj.x === obj; + } catch (e) { /* this is ES3 */ + return false; + } +}()); +var defineProperties = (function (has) { + // Define configurable, writable, and non-enumerable props + // if they don't exist. + var defineProperty; + if (supportsDescriptors) { + defineProperty = function (object, name, method, forceAssign) { + if (!forceAssign && (name in object)) { return; } + $Object.defineProperty(object, name, { + configurable: true, + enumerable: false, + writable: true, + value: method + }); + }; + } else { + defineProperty = function (object, name, method, forceAssign) { + if (!forceAssign && (name in object)) { return; } + object[name] = method; + }; + } + return function defineProperties(object, map, forceAssign) { + for (var name in map) { + if (has.call(map, name)) { + defineProperty(object, name, map[name], forceAssign); + } + } + }; +}(ObjectPrototype.hasOwnProperty)); + +// +// Util +// ====== +// + +/* replaceable with https://npmjs.com/package/es-abstract /helpers/isPrimitive */ +var isPrimitive = function isPrimitive(input) { + var type = typeof input; + return input === null || (type !== 'object' && type !== 'function'); +}; + +var isActualNaN = $Number.isNaN || function (x) { return x !== x; }; + +var ES = { + // ES5 9.4 + // http://es5.github.com/#x9.4 + // http://jsperf.com/to-integer + /* replaceable with https://npmjs.com/package/es-abstract ES5.ToInteger */ + ToInteger: function ToInteger(num) { + var n = +num; + if (isActualNaN(n)) { + n = 0; + } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) { + n = (n > 0 || -1) * Math.floor(Math.abs(n)); + } + return n; + }, + + /* replaceable with https://npmjs.com/package/es-abstract ES5.ToPrimitive */ + ToPrimitive: function ToPrimitive(input) { + var val, valueOf, toStr; + if (isPrimitive(input)) { + return input; + } + valueOf = input.valueOf; + if (isCallable(valueOf)) { + val = valueOf.call(input); + if (isPrimitive(val)) { + return val; + } + } + toStr = input.toString; + if (isCallable(toStr)) { + val = toStr.call(input); + if (isPrimitive(val)) { + return val; + } + } + throw new TypeError(); + }, + + // ES5 9.9 + // http://es5.github.com/#x9.9 + /* replaceable with https://npmjs.com/package/es-abstract ES5.ToObject */ + ToObject: function (o) { + if (o == null) { // this matches both null and undefined + throw new TypeError("can't convert " + o + ' to object'); + } + return $Object(o); + }, + + /* replaceable with https://npmjs.com/package/es-abstract ES5.ToUint32 */ + ToUint32: function ToUint32(x) { + return x >>> 0; + } +}; + +// +// Function +// ======== +// + +// ES-5 15.3.4.5 +// http://es5.github.com/#x15.3.4.5 + +var Empty = function Empty() {}; + +defineProperties(FunctionPrototype, { + bind: function bind(that) { // .length is 1 + // 1. Let Target be the this value. + var target = this; + // 2. If IsCallable(Target) is false, throw a TypeError exception. + if (!isCallable(target)) { + throw new TypeError('Function.prototype.bind called on incompatible ' + target); + } + // 3. Let A be a new (possibly empty) internal list of all of the + // argument values provided after thisArg (arg1, arg2 etc), in order. + // XXX slicedArgs will stand in for "A" if used + var args = array_slice.call(arguments, 1); // for normal call + // 4. Let F be a new native ECMAScript object. + // 11. Set the [[Prototype]] internal property of F to the standard + // built-in Function prototype object as specified in 15.3.3.1. + // 12. Set the [[Call]] internal property of F as described in + // 15.3.4.5.1. + // 13. Set the [[Construct]] internal property of F as described in + // 15.3.4.5.2. + // 14. Set the [[HasInstance]] internal property of F as described in + // 15.3.4.5.3. + var bound; + var binder = function () { + + if (this instanceof bound) { + // 15.3.4.5.2 [[Construct]] + // When the [[Construct]] internal method of a function object, + // F that was created using the bind function is called with a + // list of arguments ExtraArgs, the following steps are taken: + // 1. Let target be the value of F's [[TargetFunction]] + // internal property. + // 2. If target has no [[Construct]] internal method, a + // TypeError exception is thrown. + // 3. Let boundArgs be the value of F's [[BoundArgs]] internal + // property. + // 4. Let args be a new list containing the same values as the + // list boundArgs in the same order followed by the same + // values as the list ExtraArgs in the same order. + // 5. Return the result of calling the [[Construct]] internal + // method of target providing args as the arguments. + + var result = target.apply( + this, + array_concat.call(args, array_slice.call(arguments)) + ); + if ($Object(result) === result) { + return result; + } + return this; + + } else { + // 15.3.4.5.1 [[Call]] + // When the [[Call]] internal method of a function object, F, + // which was created using the bind function is called with a + // this value and a list of arguments ExtraArgs, the following + // steps are taken: + // 1. Let boundArgs be the value of F's [[BoundArgs]] internal + // property. + // 2. Let boundThis be the value of F's [[BoundThis]] internal + // property. + // 3. Let target be the value of F's [[TargetFunction]] internal + // property. + // 4. Let args be a new list containing the same values as the + // list boundArgs in the same order followed by the same + // values as the list ExtraArgs in the same order. + // 5. Return the result of calling the [[Call]] internal method + // of target providing boundThis as the this value and + // providing args as the arguments. + + // equiv: target.call(this, ...boundArgs, ...args) + return target.apply( + that, + array_concat.call(args, array_slice.call(arguments)) + ); + + } + + }; + + // 15. If the [[Class]] internal property of Target is "Function", then + // a. Let L be the length property of Target minus the length of A. + // b. Set the length own property of F to either 0 or L, whichever is + // larger. + // 16. Else set the length own property of F to 0. + + var boundLength = max(0, target.length - args.length); + + // 17. Set the attributes of the length own property of F to the values + // specified in 15.3.5.1. + var boundArgs = []; + for (var i = 0; i < boundLength; i++) { + array_push.call(boundArgs, '$' + i); + } + + // XXX Build a dynamic function with desired amount of arguments is the only + // way to set the length property of a function. + // In environments where Content Security Policies enabled (Chrome extensions, + // for ex.) all use of eval or Function costructor throws an exception. + // However in all of these environments Function.prototype.bind exists + // and so this code will never be executed. + bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this, arguments); }')(binder); + + if (target.prototype) { + Empty.prototype = target.prototype; + bound.prototype = new Empty(); + // Clean up dangling references. + Empty.prototype = null; + } + + // TODO + // 18. Set the [[Extensible]] internal property of F to true. + + // TODO + // 19. Let thrower be the [[ThrowTypeError]] function Object (13.2.3). + // 20. Call the [[DefineOwnProperty]] internal method of F with + // arguments "caller", PropertyDescriptor {[[Get]]: thrower, [[Set]]: + // thrower, [[Enumerable]]: false, [[Configurable]]: false}, and + // false. + // 21. Call the [[DefineOwnProperty]] internal method of F with + // arguments "arguments", PropertyDescriptor {[[Get]]: thrower, + // [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false}, + // and false. + + // TODO + // NOTE Function objects created using Function.prototype.bind do not + // have a prototype property or the [[Code]], [[FormalParameters]], and + // [[Scope]] internal properties. + // XXX can't delete prototype in pure-js. + + // 22. Return F. + return bound; + } +}); + +// _Please note: Shortcuts are defined after `Function.prototype.bind` as we +// use it in defining shortcuts. +var owns = call.bind(ObjectPrototype.hasOwnProperty); +var toStr = call.bind(ObjectPrototype.toString); +var arraySlice = call.bind(array_slice); +var arraySliceApply = apply.bind(array_slice); +var strSlice = call.bind(StringPrototype.slice); +var strSplit = call.bind(StringPrototype.split); +var strIndexOf = call.bind(StringPrototype.indexOf); +var pushCall = call.bind(array_push); +var isEnum = call.bind(ObjectPrototype.propertyIsEnumerable); +var arraySort = call.bind(ArrayPrototype.sort); + +// +// Array +// ===== +// + +var isArray = $Array.isArray || function isArray(obj) { + return toStr(obj) === '[object Array]'; +}; + +// ES5 15.4.4.12 +// http://es5.github.com/#x15.4.4.13 +// Return len+argCount. +// [bugfix, ielt8] +// IE < 8 bug: [].unshift(0) === undefined but should be "1" +var hasUnshiftReturnValueBug = [].unshift(0) !== 1; +defineProperties(ArrayPrototype, { + unshift: function () { + array_unshift.apply(this, arguments); + return this.length; + } +}, hasUnshiftReturnValueBug); + +// ES5 15.4.3.2 +// http://es5.github.com/#x15.4.3.2 +// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray +defineProperties($Array, { isArray: isArray }); + +// The IsCallable() check in the Array functions +// has been replaced with a strict check on the +// internal class of the object to trap cases where +// the provided function was actually a regular +// expression literal, which in V8 and +// JavaScriptCore is a typeof "function". Only in +// V8 are regular expression literals permitted as +// reduce parameters, so it is desirable in the +// general case for the shim to match the more +// strict and common behavior of rejecting regular +// expressions. + +// ES5 15.4.4.18 +// http://es5.github.com/#x15.4.4.18 +// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/forEach + +// Check failure of by-index access of string characters (IE < 9) +// and failure of `0 in boxedString` (Rhino) +var boxedString = $Object('a'); +var splitString = boxedString[0] !== 'a' || !(0 in boxedString); + +var properlyBoxesContext = function properlyBoxed(method) { + // Check node 0.6.21 bug where third parameter is not boxed + var properlyBoxesNonStrict = true; + var properlyBoxesStrict = true; + var threwException = false; + if (method) { + try { + method.call('foo', function (_, __, context) { + if (typeof context !== 'object') { properlyBoxesNonStrict = false; } + }); + + method.call([1], function () { + 'use strict'; + + properlyBoxesStrict = typeof this === 'string'; + }, 'x'); + } catch (e) { + threwException = true; + } + } + return !!method && !threwException && properlyBoxesNonStrict && properlyBoxesStrict; +}; + +defineProperties(ArrayPrototype, { + forEach: function forEach(callbackfn/*, thisArg*/) { + var object = ES.ToObject(this); + var self = splitString && isString(this) ? strSplit(this, '') : object; + var i = -1; + var length = ES.ToUint32(self.length); + var T; + if (arguments.length > 1) { + T = arguments[1]; + } + + // If no callback function or if callback is not a callable function + if (!isCallable(callbackfn)) { + throw new TypeError('Array.prototype.forEach callback must be a function'); + } + + while (++i < length) { + if (i in self) { + // Invoke the callback function with call, passing arguments: + // context, property value, property key, thisArg object + if (typeof T === 'undefined') { + callbackfn(self[i], i, object); + } else { + callbackfn.call(T, self[i], i, object); + } + } + } + } +}, !properlyBoxesContext(ArrayPrototype.forEach)); + +// ES5 15.4.4.19 +// http://es5.github.com/#x15.4.4.19 +// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map +defineProperties(ArrayPrototype, { + map: function map(callbackfn/*, thisArg*/) { + var object = ES.ToObject(this); + var self = splitString && isString(this) ? strSplit(this, '') : object; + var length = ES.ToUint32(self.length); + var result = $Array(length); + var T; + if (arguments.length > 1) { + T = arguments[1]; + } + + // If no callback function or if callback is not a callable function + if (!isCallable(callbackfn)) { + throw new TypeError('Array.prototype.map callback must be a function'); + } + + for (var i = 0; i < length; i++) { + if (i in self) { + if (typeof T === 'undefined') { + result[i] = callbackfn(self[i], i, object); + } else { + result[i] = callbackfn.call(T, self[i], i, object); + } + } + } + return result; + } +}, !properlyBoxesContext(ArrayPrototype.map)); + +// ES5 15.4.4.20 +// http://es5.github.com/#x15.4.4.20 +// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/filter +defineProperties(ArrayPrototype, { + filter: function filter(callbackfn/*, thisArg*/) { + var object = ES.ToObject(this); + var self = splitString && isString(this) ? strSplit(this, '') : object; + var length = ES.ToUint32(self.length); + var result = []; + var value; + var T; + if (arguments.length > 1) { + T = arguments[1]; + } + + // If no callback function or if callback is not a callable function + if (!isCallable(callbackfn)) { + throw new TypeError('Array.prototype.filter callback must be a function'); + } + + for (var i = 0; i < length; i++) { + if (i in self) { + value = self[i]; + if (typeof T === 'undefined' ? callbackfn(value, i, object) : callbackfn.call(T, value, i, object)) { + pushCall(result, value); + } + } + } + return result; + } +}, !properlyBoxesContext(ArrayPrototype.filter)); + +// ES5 15.4.4.16 +// http://es5.github.com/#x15.4.4.16 +// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/every +defineProperties(ArrayPrototype, { + every: function every(callbackfn/*, thisArg*/) { + var object = ES.ToObject(this); + var self = splitString && isString(this) ? strSplit(this, '') : object; + var length = ES.ToUint32(self.length); + var T; + if (arguments.length > 1) { + T = arguments[1]; + } + + // If no callback function or if callback is not a callable function + if (!isCallable(callbackfn)) { + throw new TypeError('Array.prototype.every callback must be a function'); + } + + for (var i = 0; i < length; i++) { + if (i in self && !(typeof T === 'undefined' ? callbackfn(self[i], i, object) : callbackfn.call(T, self[i], i, object))) { + return false; + } + } + return true; + } +}, !properlyBoxesContext(ArrayPrototype.every)); + +// ES5 15.4.4.17 +// http://es5.github.com/#x15.4.4.17 +// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some +defineProperties(ArrayPrototype, { + some: function some(callbackfn/*, thisArg */) { + var object = ES.ToObject(this); + var self = splitString && isString(this) ? strSplit(this, '') : object; + var length = ES.ToUint32(self.length); + var T; + if (arguments.length > 1) { + T = arguments[1]; + } + + // If no callback function or if callback is not a callable function + if (!isCallable(callbackfn)) { + throw new TypeError('Array.prototype.some callback must be a function'); + } + + for (var i = 0; i < length; i++) { + if (i in self && (typeof T === 'undefined' ? callbackfn(self[i], i, object) : callbackfn.call(T, self[i], i, object))) { + return true; + } + } + return false; + } +}, !properlyBoxesContext(ArrayPrototype.some)); + +// ES5 15.4.4.21 +// http://es5.github.com/#x15.4.4.21 +// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduce +var reduceCoercesToObject = false; +if (ArrayPrototype.reduce) { + reduceCoercesToObject = typeof ArrayPrototype.reduce.call('es5', function (_, __, ___, list) { return list; }) === 'object'; +} +defineProperties(ArrayPrototype, { + reduce: function reduce(callbackfn/*, initialValue*/) { + var object = ES.ToObject(this); + var self = splitString && isString(this) ? strSplit(this, '') : object; + var length = ES.ToUint32(self.length); + + // If no callback function or if callback is not a callable function + if (!isCallable(callbackfn)) { + throw new TypeError('Array.prototype.reduce callback must be a function'); + } + + // no value to return if no initial value and an empty array + if (length === 0 && arguments.length === 1) { + throw new TypeError('reduce of empty array with no initial value'); + } + + var i = 0; + var result; + if (arguments.length >= 2) { + result = arguments[1]; + } else { + do { + if (i in self) { + result = self[i++]; + break; + } + + // if array contains no values, no initial value to return + if (++i >= length) { + throw new TypeError('reduce of empty array with no initial value'); + } + } while (true); + } + + for (; i < length; i++) { + if (i in self) { + result = callbackfn(result, self[i], i, object); + } + } + + return result; + } +}, !reduceCoercesToObject); + +// ES5 15.4.4.22 +// http://es5.github.com/#x15.4.4.22 +// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduceRight +var reduceRightCoercesToObject = false; +if (ArrayPrototype.reduceRight) { + reduceRightCoercesToObject = typeof ArrayPrototype.reduceRight.call('es5', function (_, __, ___, list) { return list; }) === 'object'; +} +defineProperties(ArrayPrototype, { + reduceRight: function reduceRight(callbackfn/*, initial*/) { + var object = ES.ToObject(this); + var self = splitString && isString(this) ? strSplit(this, '') : object; + var length = ES.ToUint32(self.length); + + // If no callback function or if callback is not a callable function + if (!isCallable(callbackfn)) { + throw new TypeError('Array.prototype.reduceRight callback must be a function'); + } + + // no value to return if no initial value, empty array + if (length === 0 && arguments.length === 1) { + throw new TypeError('reduceRight of empty array with no initial value'); + } + + var result; + var i = length - 1; + if (arguments.length >= 2) { + result = arguments[1]; + } else { + do { + if (i in self) { + result = self[i--]; + break; + } + + // if array contains no values, no initial value to return + if (--i < 0) { + throw new TypeError('reduceRight of empty array with no initial value'); + } + } while (true); + } + + if (i < 0) { + return result; + } + + do { + if (i in self) { + result = callbackfn(result, self[i], i, object); + } + } while (i--); + + return result; + } +}, !reduceRightCoercesToObject); + +// ES5 15.4.4.14 +// http://es5.github.com/#x15.4.4.14 +// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf +var hasFirefox2IndexOfBug = ArrayPrototype.indexOf && [0, 1].indexOf(1, 2) !== -1; +defineProperties(ArrayPrototype, { + indexOf: function indexOf(searchElement/*, fromIndex */) { + var self = splitString && isString(this) ? strSplit(this, '') : ES.ToObject(this); + var length = ES.ToUint32(self.length); + + if (length === 0) { + return -1; + } + + var i = 0; + if (arguments.length > 1) { + i = ES.ToInteger(arguments[1]); + } + + // handle negative indices + i = i >= 0 ? i : max(0, length + i); + for (; i < length; i++) { + if (i in self && self[i] === searchElement) { + return i; + } + } + return -1; + } +}, hasFirefox2IndexOfBug); + +// ES5 15.4.4.15 +// http://es5.github.com/#x15.4.4.15 +// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/lastIndexOf +var hasFirefox2LastIndexOfBug = ArrayPrototype.lastIndexOf && [0, 1].lastIndexOf(0, -3) !== -1; +defineProperties(ArrayPrototype, { + lastIndexOf: function lastIndexOf(searchElement/*, fromIndex */) { + var self = splitString && isString(this) ? strSplit(this, '') : ES.ToObject(this); + var length = ES.ToUint32(self.length); + + if (length === 0) { + return -1; + } + var i = length - 1; + if (arguments.length > 1) { + i = min(i, ES.ToInteger(arguments[1])); + } + // handle negative indices + i = i >= 0 ? i : length - Math.abs(i); + for (; i >= 0; i--) { + if (i in self && searchElement === self[i]) { + return i; + } + } + return -1; + } +}, hasFirefox2LastIndexOfBug); + +// ES5 15.4.4.12 +// http://es5.github.com/#x15.4.4.12 +var spliceNoopReturnsEmptyArray = (function () { + var a = [1, 2]; + var result = a.splice(); + return a.length === 2 && isArray(result) && result.length === 0; +}()); +defineProperties(ArrayPrototype, { + // Safari 5.0 bug where .splice() returns undefined + splice: function splice(start, deleteCount) { + if (arguments.length === 0) { + return []; + } else { + return array_splice.apply(this, arguments); + } + } +}, !spliceNoopReturnsEmptyArray); + +var spliceWorksWithEmptyObject = (function () { + var obj = {}; + ArrayPrototype.splice.call(obj, 0, 0, 1); + return obj.length === 1; +}()); +defineProperties(ArrayPrototype, { + splice: function splice(start, deleteCount) { + if (arguments.length === 0) { return []; } + var args = arguments; + this.length = max(ES.ToInteger(this.length), 0); + if (arguments.length > 0 && typeof deleteCount !== 'number') { + args = arraySlice(arguments); + if (args.length < 2) { + pushCall(args, this.length - start); + } else { + args[1] = ES.ToInteger(deleteCount); + } + } + return array_splice.apply(this, args); + } +}, !spliceWorksWithEmptyObject); +var spliceWorksWithLargeSparseArrays = (function () { + // Per https://github.com/es-shims/es5-shim/issues/295 + // Safari 7/8 breaks with sparse arrays of size 1e5 or greater + var arr = new $Array(1e5); + // note: the index MUST be 8 or larger or the test will false pass + arr[8] = 'x'; + arr.splice(1, 1); + // note: this test must be defined *after* the indexOf shim + // per https://github.com/es-shims/es5-shim/issues/313 + return arr.indexOf('x') === 7; +}()); +var spliceWorksWithSmallSparseArrays = (function () { + // Per https://github.com/es-shims/es5-shim/issues/295 + // Opera 12.15 breaks on this, no idea why. + var n = 256; + var arr = []; + arr[n] = 'a'; + arr.splice(n + 1, 0, 'b'); + return arr[n] === 'a'; +}()); +defineProperties(ArrayPrototype, { + splice: function splice(start, deleteCount) { + var O = ES.ToObject(this); + var A = []; + var len = ES.ToUint32(O.length); + var relativeStart = ES.ToInteger(start); + var actualStart = relativeStart < 0 ? max((len + relativeStart), 0) : min(relativeStart, len); + var actualDeleteCount = min(max(ES.ToInteger(deleteCount), 0), len - actualStart); + + var k = 0; + var from; + while (k < actualDeleteCount) { + from = $String(actualStart + k); + if (owns(O, from)) { + A[k] = O[from]; + } + k += 1; + } + + var items = arraySlice(arguments, 2); + var itemCount = items.length; + var to; + if (itemCount < actualDeleteCount) { + k = actualStart; + while (k < (len - actualDeleteCount)) { + from = $String(k + actualDeleteCount); + to = $String(k + itemCount); + if (owns(O, from)) { + O[to] = O[from]; + } else { + delete O[to]; + } + k += 1; + } + k = len; + while (k > (len - actualDeleteCount + itemCount)) { + delete O[k - 1]; + k -= 1; + } + } else if (itemCount > actualDeleteCount) { + k = len - actualDeleteCount; + while (k > actualStart) { + from = $String(k + actualDeleteCount - 1); + to = $String(k + itemCount - 1); + if (owns(O, from)) { + O[to] = O[from]; + } else { + delete O[to]; + } + k -= 1; + } + } + k = actualStart; + for (var i = 0; i < items.length; ++i) { + O[k] = items[i]; + k += 1; + } + O.length = len - actualDeleteCount + itemCount; + + return A; + } +}, !spliceWorksWithLargeSparseArrays || !spliceWorksWithSmallSparseArrays); + +var originalJoin = ArrayPrototype.join; +var hasStringJoinBug; +try { + hasStringJoinBug = Array.prototype.join.call('123', ',') !== '1,2,3'; +} catch (e) { + hasStringJoinBug = true; +} +if (hasStringJoinBug) { + defineProperties(ArrayPrototype, { + join: function join(separator) { + var sep = typeof separator === 'undefined' ? ',' : separator; + return originalJoin.call(isString(this) ? strSplit(this, '') : this, sep); + } + }, hasStringJoinBug); +} + +var hasJoinUndefinedBug = [1, 2].join(undefined) !== '1,2'; +if (hasJoinUndefinedBug) { + defineProperties(ArrayPrototype, { + join: function join(separator) { + var sep = typeof separator === 'undefined' ? ',' : separator; + return originalJoin.call(this, sep); + } + }, hasJoinUndefinedBug); +} + +var pushShim = function push(item) { + var O = ES.ToObject(this); + var n = ES.ToUint32(O.length); + var i = 0; + while (i < arguments.length) { + O[n + i] = arguments[i]; + i += 1; + } + O.length = n + i; + return n + i; +}; + +var pushIsNotGeneric = (function () { + var obj = {}; + var result = Array.prototype.push.call(obj, undefined); + return result !== 1 || obj.length !== 1 || typeof obj[0] !== 'undefined' || !owns(obj, 0); +}()); +defineProperties(ArrayPrototype, { + push: function push(item) { + if (isArray(this)) { + return array_push.apply(this, arguments); + } + return pushShim.apply(this, arguments); + } +}, pushIsNotGeneric); + +// This fixes a very weird bug in Opera 10.6 when pushing `undefined +var pushUndefinedIsWeird = (function () { + var arr = []; + var result = arr.push(undefined); + return result !== 1 || arr.length !== 1 || typeof arr[0] !== 'undefined' || !owns(arr, 0); +}()); +defineProperties(ArrayPrototype, { push: pushShim }, pushUndefinedIsWeird); + +// ES5 15.2.3.14 +// http://es5.github.io/#x15.4.4.10 +// Fix boxed string bug +defineProperties(ArrayPrototype, { + slice: function (start, end) { + var arr = isString(this) ? strSplit(this, '') : this; + return arraySliceApply(arr, arguments); + } +}, splitString); + +var sortIgnoresNonFunctions = (function () { + try { + [1, 2].sort(null); + [1, 2].sort({}); + return true; + } catch (e) { /**/ } + return false; +}()); +var sortThrowsOnRegex = (function () { + // this is a problem in Firefox 4, in which `typeof /a/ === 'function'` + try { + [1, 2].sort(/a/); + return false; + } catch (e) { /**/ } + return true; +}()); +var sortIgnoresUndefined = (function () { + // applies in IE 8, for one. + try { + [1, 2].sort(undefined); + return true; + } catch (e) { /**/ } + return false; +}()); +defineProperties(ArrayPrototype, { + sort: function sort(compareFn) { + if (typeof compareFn === 'undefined') { + return arraySort(this); + } + if (!isCallable(compareFn)) { + throw new TypeError('Array.prototype.sort callback must be a function'); + } + return arraySort(this, compareFn); + } +}, sortIgnoresNonFunctions || !sortIgnoresUndefined || !sortThrowsOnRegex); + +// +// Object +// ====== +// + +// ES5 15.2.3.14 +// http://es5.github.com/#x15.2.3.14 + +// http://whattheheadsaid.com/2010/10/a-safer-object-keys-compatibility-implementation +var hasDontEnumBug = !({ 'toString': null }).propertyIsEnumerable('toString'); +var hasProtoEnumBug = function () {}.propertyIsEnumerable('prototype'); +var hasStringEnumBug = !owns('x', '0'); +var equalsConstructorPrototype = function (o) { + var ctor = o.constructor; + return ctor && ctor.prototype === o; +}; +var blacklistedKeys = { + $window: true, + $console: true, + $parent: true, + $self: true, + $frame: true, + $frames: true, + $frameElement: true, + $webkitIndexedDB: true, + $webkitStorageInfo: true, + $external: true +}; +var hasAutomationEqualityBug = (function () { + /* globals window */ + if (typeof window === 'undefined') { return false; } + for (var k in window) { + try { + if (!blacklistedKeys['$' + k] && owns(window, k) && window[k] !== null && typeof window[k] === 'object') { + equalsConstructorPrototype(window[k]); + } + } catch (e) { + return true; + } + } + return false; +}()); +var equalsConstructorPrototypeIfNotBuggy = function (object) { + if (typeof window === 'undefined' || !hasAutomationEqualityBug) { return equalsConstructorPrototype(object); } + try { + return equalsConstructorPrototype(object); + } catch (e) { + return false; + } +}; +var dontEnums = [ + 'toString', + 'toLocaleString', + 'valueOf', + 'hasOwnProperty', + 'isPrototypeOf', + 'propertyIsEnumerable', + 'constructor' +]; +var dontEnumsLength = dontEnums.length; + +// taken directly from https://github.com/ljharb/is-arguments/blob/master/index.js +// can be replaced with require('is-arguments') if we ever use a build process instead +var isStandardArguments = function isArguments(value) { + return toStr(value) === '[object Arguments]'; +}; +var isLegacyArguments = function isArguments(value) { + return value !== null && + typeof value === 'object' && + typeof value.length === 'number' && + value.length >= 0 && + !isArray(value) && + isCallable(value.callee); +}; +var isArguments = isStandardArguments(arguments) ? isStandardArguments : isLegacyArguments; + +defineProperties($Object, { + keys: function keys(object) { + var isFn = isCallable(object); + var isArgs = isArguments(object); + var isObject = object !== null && typeof object === 'object'; + var isStr = isObject && isString(object); + + if (!isObject && !isFn && !isArgs) { + throw new TypeError('Object.keys called on a non-object'); + } + + var theKeys = []; + var skipProto = hasProtoEnumBug && isFn; + if ((isStr && hasStringEnumBug) || isArgs) { + for (var i = 0; i < object.length; ++i) { + pushCall(theKeys, $String(i)); + } + } + + if (!isArgs) { + for (var name in object) { + if (!(skipProto && name === 'prototype') && owns(object, name)) { + pushCall(theKeys, $String(name)); + } + } + } + + if (hasDontEnumBug) { + var skipConstructor = equalsConstructorPrototypeIfNotBuggy(object); + for (var j = 0; j < dontEnumsLength; j++) { + var dontEnum = dontEnums[j]; + if (!(skipConstructor && dontEnum === 'constructor') && owns(object, dontEnum)) { + pushCall(theKeys, dontEnum); + } + } + } + return theKeys; + } +}); + +var keysWorksWithArguments = $Object.keys && (function () { + // Safari 5.0 bug + return $Object.keys(arguments).length === 2; +}(1, 2)); +var keysHasArgumentsLengthBug = $Object.keys && (function () { + var argKeys = $Object.keys(arguments); + return arguments.length !== 1 || argKeys.length !== 1 || argKeys[0] !== 1; +}(1)); +var originalKeys = $Object.keys; +defineProperties($Object, { + keys: function keys(object) { + if (isArguments(object)) { + return originalKeys(arraySlice(object)); + } else { + return originalKeys(object); + } + } +}, !keysWorksWithArguments || keysHasArgumentsLengthBug); + +// +// Date +// ==== +// + +var hasNegativeMonthYearBug = new Date(-3509827329600292).getUTCMonth() !== 0; +var aNegativeTestDate = new Date(-1509842289600292); +var aPositiveTestDate = new Date(1449662400000); +var hasToUTCStringFormatBug = aNegativeTestDate.toUTCString() !== 'Mon, 01 Jan -45875 11:59:59 GMT'; +var hasToDateStringFormatBug; +var hasToStringFormatBug; +var timeZoneOffset = aNegativeTestDate.getTimezoneOffset(); +if (timeZoneOffset < -720) { + hasToDateStringFormatBug = aNegativeTestDate.toDateString() !== 'Tue Jan 02 -45875'; + hasToStringFormatBug = !(/^Thu Dec 10 2015 \d\d:\d\d:\d\d GMT[-\+]\d\d\d\d(?: |$)/).test(aPositiveTestDate.toString()); +} else { + hasToDateStringFormatBug = aNegativeTestDate.toDateString() !== 'Mon Jan 01 -45875'; + hasToStringFormatBug = !(/^Wed Dec 09 2015 \d\d:\d\d:\d\d GMT[-\+]\d\d\d\d(?: |$)/).test(aPositiveTestDate.toString()); +} + +var originalGetFullYear = call.bind(Date.prototype.getFullYear); +var originalGetMonth = call.bind(Date.prototype.getMonth); +var originalGetDate = call.bind(Date.prototype.getDate); +var originalGetUTCFullYear = call.bind(Date.prototype.getUTCFullYear); +var originalGetUTCMonth = call.bind(Date.prototype.getUTCMonth); +var originalGetUTCDate = call.bind(Date.prototype.getUTCDate); +var originalGetUTCDay = call.bind(Date.prototype.getUTCDay); +var originalGetUTCHours = call.bind(Date.prototype.getUTCHours); +var originalGetUTCMinutes = call.bind(Date.prototype.getUTCMinutes); +var originalGetUTCSeconds = call.bind(Date.prototype.getUTCSeconds); +var originalGetUTCMilliseconds = call.bind(Date.prototype.getUTCMilliseconds); +var dayName = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri']; +var monthName = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; +var daysInMonth = function daysInMonth(month, year) { + return originalGetDate(new Date(year, month, 0)); +}; + +defineProperties(Date.prototype, { + getFullYear: function getFullYear() { + if (!this || !(this instanceof Date)) { + throw new TypeError('this is not a Date object.'); + } + var year = originalGetFullYear(this); + if (year < 0 && originalGetMonth(this) > 11) { + return year + 1; + } + return year; + }, + getMonth: function getMonth() { + if (!this || !(this instanceof Date)) { + throw new TypeError('this is not a Date object.'); + } + var year = originalGetFullYear(this); + var month = originalGetMonth(this); + if (year < 0 && month > 11) { + return 0; + } + return month; + }, + getDate: function getDate() { + if (!this || !(this instanceof Date)) { + throw new TypeError('this is not a Date object.'); + } + var year = originalGetFullYear(this); + var month = originalGetMonth(this); + var date = originalGetDate(this); + if (year < 0 && month > 11) { + if (month === 12) { + return date; + } + var days = daysInMonth(0, year + 1); + return (days - date) + 1; + } + return date; + }, + getUTCFullYear: function getUTCFullYear() { + if (!this || !(this instanceof Date)) { + throw new TypeError('this is not a Date object.'); + } + var year = originalGetUTCFullYear(this); + if (year < 0 && originalGetUTCMonth(this) > 11) { + return year + 1; + } + return year; + }, + getUTCMonth: function getUTCMonth() { + if (!this || !(this instanceof Date)) { + throw new TypeError('this is not a Date object.'); + } + var year = originalGetUTCFullYear(this); + var month = originalGetUTCMonth(this); + if (year < 0 && month > 11) { + return 0; + } + return month; + }, + getUTCDate: function getUTCDate() { + if (!this || !(this instanceof Date)) { + throw new TypeError('this is not a Date object.'); + } + var year = originalGetUTCFullYear(this); + var month = originalGetUTCMonth(this); + var date = originalGetUTCDate(this); + if (year < 0 && month > 11) { + if (month === 12) { + return date; + } + var days = daysInMonth(0, year + 1); + return (days - date) + 1; + } + return date; + } +}, hasNegativeMonthYearBug); + +defineProperties(Date.prototype, { + toUTCString: function toUTCString() { + if (!this || !(this instanceof Date)) { + throw new TypeError('this is not a Date object.'); + } + var day = originalGetUTCDay(this); + var date = originalGetUTCDate(this); + var month = originalGetUTCMonth(this); + var year = originalGetUTCFullYear(this); + var hour = originalGetUTCHours(this); + var minute = originalGetUTCMinutes(this); + var second = originalGetUTCSeconds(this); + return dayName[day] + ', ' + + (date < 10 ? '0' + date : date) + ' ' + + monthName[month] + ' ' + + year + ' ' + + (hour < 10 ? '0' + hour : hour) + ':' + + (minute < 10 ? '0' + minute : minute) + ':' + + (second < 10 ? '0' + second : second) + ' GMT'; + } +}, hasNegativeMonthYearBug || hasToUTCStringFormatBug); + +// Opera 12 has `,` +defineProperties(Date.prototype, { + toDateString: function toDateString() { + if (!this || !(this instanceof Date)) { + throw new TypeError('this is not a Date object.'); + } + var day = this.getDay(); + var date = this.getDate(); + var month = this.getMonth(); + var year = this.getFullYear(); + return dayName[day] + ' ' + + monthName[month] + ' ' + + (date < 10 ? '0' + date : date) + ' ' + + year; + } +}, hasNegativeMonthYearBug || hasToDateStringFormatBug); + +// can't use defineProperties here because of toString enumeration issue in IE <= 8 +if (hasNegativeMonthYearBug || hasToStringFormatBug) { + Date.prototype.toString = function toString() { + if (!this || !(this instanceof Date)) { + throw new TypeError('this is not a Date object.'); + } + var day = this.getDay(); + var date = this.getDate(); + var month = this.getMonth(); + var year = this.getFullYear(); + var hour = this.getHours(); + var minute = this.getMinutes(); + var second = this.getSeconds(); + var timezoneOffset = this.getTimezoneOffset(); + var hoursOffset = Math.floor(Math.abs(timezoneOffset) / 60); + var minutesOffset = Math.floor(Math.abs(timezoneOffset) % 60); + return dayName[day] + ' ' + + monthName[month] + ' ' + + (date < 10 ? '0' + date : date) + ' ' + + year + ' ' + + (hour < 10 ? '0' + hour : hour) + ':' + + (minute < 10 ? '0' + minute : minute) + ':' + + (second < 10 ? '0' + second : second) + ' GMT' + + (timezoneOffset > 0 ? '-' : '+') + + (hoursOffset < 10 ? '0' + hoursOffset : hoursOffset) + + (minutesOffset < 10 ? '0' + minutesOffset : minutesOffset); + }; + if (supportsDescriptors) { + $Object.defineProperty(Date.prototype, 'toString', { + configurable: true, + enumerable: false, + writable: true + }); + } +} + +// ES5 15.9.5.43 +// http://es5.github.com/#x15.9.5.43 +// This function returns a String value represent the instance in time +// represented by this Date object. The format of the String is the Date Time +// string format defined in 15.9.1.15. All fields are present in the String. +// The time zone is always UTC, denoted by the suffix Z. If the time value of +// this object is not a finite Number a RangeError exception is thrown. +var negativeDate = -62198755200000; +var negativeYearString = '-000001'; +var hasNegativeDateBug = Date.prototype.toISOString && new Date(negativeDate).toISOString().indexOf(negativeYearString) === -1; +var hasSafari51DateBug = Date.prototype.toISOString && new Date(-1).toISOString() !== '1969-12-31T23:59:59.999Z'; + +defineProperties(Date.prototype, { + toISOString: function toISOString() { + if (!isFinite(this)) { + throw new RangeError('Date.prototype.toISOString called on non-finite value.'); + } + + var year = originalGetUTCFullYear(this); + + var month = originalGetUTCMonth(this); + // see https://github.com/es-shims/es5-shim/issues/111 + year += Math.floor(month / 12); + month = (month % 12 + 12) % 12; + + // the date time string format is specified in 15.9.1.15. + var result = [month + 1, originalGetUTCDate(this), originalGetUTCHours(this), originalGetUTCMinutes(this), originalGetUTCSeconds(this)]; + year = ( + (year < 0 ? '-' : (year > 9999 ? '+' : '')) + + strSlice('00000' + Math.abs(year), (0 <= year && year <= 9999) ? -4 : -6) + ); + + for (var i = 0; i < result.length; ++i) { + // pad months, days, hours, minutes, and seconds to have two digits. + result[i] = strSlice('00' + result[i], -2); + } + // pad milliseconds to have three digits. + return ( + year + '-' + arraySlice(result, 0, 2).join('-') + + 'T' + arraySlice(result, 2).join(':') + '.' + + strSlice('000' + originalGetUTCMilliseconds(this), -3) + 'Z' + ); + } +}, hasNegativeDateBug || hasSafari51DateBug); + +// ES5 15.9.5.44 +// http://es5.github.com/#x15.9.5.44 +// This function provides a String representation of a Date object for use by +// JSON.stringify (15.12.3). +var dateToJSONIsSupported = (function () { + try { + return Date.prototype.toJSON && + new Date(NaN).toJSON() === null && + new Date(negativeDate).toJSON().indexOf(negativeYearString) !== -1 && + Date.prototype.toJSON.call({ // generic + toISOString: function () { return true; } + }); + } catch (e) { + return false; + } +}()); +if (!dateToJSONIsSupported) { + Date.prototype.toJSON = function toJSON(key) { + // When the toJSON method is called with argument key, the following + // steps are taken: + + // 1. Let O be the result of calling ToObject, giving it the this + // value as its argument. + // 2. Let tv be ES.ToPrimitive(O, hint Number). + var O = $Object(this); + var tv = ES.ToPrimitive(O); + // 3. If tv is a Number and is not finite, return null. + if (typeof tv === 'number' && !isFinite(tv)) { + return null; + } + // 4. Let toISO be the result of calling the [[Get]] internal method of + // O with argument "toISOString". + var toISO = O.toISOString; + // 5. If IsCallable(toISO) is false, throw a TypeError exception. + if (!isCallable(toISO)) { + throw new TypeError('toISOString property is not callable'); + } + // 6. Return the result of calling the [[Call]] internal method of + // toISO with O as the this value and an empty argument list. + return toISO.call(O); + + // NOTE 1 The argument is ignored. + + // NOTE 2 The toJSON function is intentionally generic; it does not + // require that its this value be a Date object. Therefore, it can be + // transferred to other kinds of objects for use as a method. However, + // it does require that any such object have a toISOString method. An + // object is free to use the argument key to filter its + // stringification. + }; +} + +// ES5 15.9.4.2 +// http://es5.github.com/#x15.9.4.2 +// based on work shared by Daniel Friesen (dantman) +// http://gist.github.com/303249 +var supportsExtendedYears = Date.parse('+033658-09-27T01:46:40.000Z') === 1e15; +var acceptsInvalidDates = !isNaN(Date.parse('2012-04-04T24:00:00.500Z')) || !isNaN(Date.parse('2012-11-31T23:59:59.000Z')) || !isNaN(Date.parse('2012-12-31T23:59:60.000Z')); +var doesNotParseY2KNewYear = isNaN(Date.parse('2000-01-01T00:00:00.000Z')); +if (doesNotParseY2KNewYear || acceptsInvalidDates || !supportsExtendedYears) { + // XXX global assignment won't work in embeddings that use + // an alternate object for the context. + /* global Date: true */ + /* eslint-disable no-undef */ + var maxSafeUnsigned32Bit = Math.pow(2, 31) - 1; + var hasSafariSignedIntBug = isActualNaN(new Date(1970, 0, 1, 0, 0, 0, maxSafeUnsigned32Bit + 1).getTime()); + Date = (function (NativeDate) { + /* eslint-enable no-undef */ + // Date.length === 7 + var DateShim = function Date(Y, M, D, h, m, s, ms) { + var length = arguments.length; + var date; + if (this instanceof NativeDate) { + var seconds = s; + var millis = ms; + if (hasSafariSignedIntBug && length >= 7 && ms > maxSafeUnsigned32Bit) { + // work around a Safari 8/9 bug where it treats the seconds as signed + var msToShift = Math.floor(ms / maxSafeUnsigned32Bit) * maxSafeUnsigned32Bit; + var sToShift = Math.floor(msToShift / 1e3); + seconds += sToShift; + millis -= sToShift * 1e3; + } + date = length === 1 && $String(Y) === Y ? // isString(Y) + // We explicitly pass it through parse: + new NativeDate(DateShim.parse(Y)) : + // We have to manually make calls depending on argument + // length here + length >= 7 ? new NativeDate(Y, M, D, h, m, seconds, millis) : + length >= 6 ? new NativeDate(Y, M, D, h, m, seconds) : + length >= 5 ? new NativeDate(Y, M, D, h, m) : + length >= 4 ? new NativeDate(Y, M, D, h) : + length >= 3 ? new NativeDate(Y, M, D) : + length >= 2 ? new NativeDate(Y, M) : + length >= 1 ? new NativeDate(Y) : + new NativeDate(); + } else { + date = NativeDate.apply(this, arguments); + } + if (!isPrimitive(date)) { + // Prevent mixups with unfixed Date object + defineProperties(date, { constructor: DateShim }, true); + } + return date; + }; + + // 15.9.1.15 Date Time String Format. + var isoDateExpression = new RegExp('^' + + '(\\d{4}|[+-]\\d{6})' + // four-digit year capture or sign + + // 6-digit extended year + '(?:-(\\d{2})' + // optional month capture + '(?:-(\\d{2})' + // optional day capture + '(?:' + // capture hours:minutes:seconds.milliseconds + 'T(\\d{2})' + // hours capture + ':(\\d{2})' + // minutes capture + '(?:' + // optional :seconds.milliseconds + ':(\\d{2})' + // seconds capture + '(?:(\\.\\d{1,}))?' + // milliseconds capture + ')?' + + '(' + // capture UTC offset component + 'Z|' + // UTC capture + '(?:' + // offset specifier +/-hours:minutes + '([-+])' + // sign capture + '(\\d{2})' + // hours offset capture + ':(\\d{2})' + // minutes offset capture + ')' + + ')?)?)?)?' + + '$'); + + var months = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]; + + var dayFromMonth = function dayFromMonth(year, month) { + var t = month > 1 ? 1 : 0; + return ( + months[month] + + Math.floor((year - 1969 + t) / 4) - + Math.floor((year - 1901 + t) / 100) + + Math.floor((year - 1601 + t) / 400) + + 365 * (year - 1970) + ); + }; + + var toUTC = function toUTC(t) { + var s = 0; + var ms = t; + if (hasSafariSignedIntBug && ms > maxSafeUnsigned32Bit) { + // work around a Safari 8/9 bug where it treats the seconds as signed + var msToShift = Math.floor(ms / maxSafeUnsigned32Bit) * maxSafeUnsigned32Bit; + var sToShift = Math.floor(msToShift / 1e3); + s += sToShift; + ms -= sToShift * 1e3; + } + return $Number(new NativeDate(1970, 0, 1, 0, 0, s, ms)); + }; + + // Copy any custom methods a 3rd party library may have added + for (var key in NativeDate) { + if (owns(NativeDate, key)) { + DateShim[key] = NativeDate[key]; + } + } + + // Copy "native" methods explicitly; they may be non-enumerable + defineProperties(DateShim, { + now: NativeDate.now, + UTC: NativeDate.UTC + }, true); + DateShim.prototype = NativeDate.prototype; + defineProperties(DateShim.prototype, { + constructor: DateShim + }, true); + + // Upgrade Date.parse to handle simplified ISO 8601 strings + var parseShim = function parse(string) { + var match = isoDateExpression.exec(string); + if (match) { + // parse months, days, hours, minutes, seconds, and milliseconds + // provide default values if necessary + // parse the UTC offset component + var year = $Number(match[1]), + month = $Number(match[2] || 1) - 1, + day = $Number(match[3] || 1) - 1, + hour = $Number(match[4] || 0), + minute = $Number(match[5] || 0), + second = $Number(match[6] || 0), + millisecond = Math.floor($Number(match[7] || 0) * 1000), + // When time zone is missed, local offset should be used + // (ES 5.1 bug) + // see https://bugs.ecmascript.org/show_bug.cgi?id=112 + isLocalTime = Boolean(match[4] && !match[8]), + signOffset = match[9] === '-' ? 1 : -1, + hourOffset = $Number(match[10] || 0), + minuteOffset = $Number(match[11] || 0), + result; + var hasMinutesOrSecondsOrMilliseconds = minute > 0 || second > 0 || millisecond > 0; + if ( + hour < (hasMinutesOrSecondsOrMilliseconds ? 24 : 25) && + minute < 60 && second < 60 && millisecond < 1000 && + month > -1 && month < 12 && hourOffset < 24 && + minuteOffset < 60 && // detect invalid offsets + day > -1 && + day < (dayFromMonth(year, month + 1) - dayFromMonth(year, month)) + ) { + result = ( + (dayFromMonth(year, month) + day) * 24 + + hour + + hourOffset * signOffset + ) * 60; + result = ( + (result + minute + minuteOffset * signOffset) * 60 + + second + ) * 1000 + millisecond; + if (isLocalTime) { + result = toUTC(result); + } + if (-8.64e15 <= result && result <= 8.64e15) { + return result; + } + } + return NaN; + } + return NativeDate.parse.apply(this, arguments); + }; + defineProperties(DateShim, { parse: parseShim }); + + return DateShim; + }(Date)); + /* global Date: false */ +} + +// ES5 15.9.4.4 +// http://es5.github.com/#x15.9.4.4 +if (!Date.now) { + Date.now = function now() { + return new Date().getTime(); + }; +} + +// +// Number +// ====== +// + +// ES5.1 15.7.4.5 +// http://es5.github.com/#x15.7.4.5 +var hasToFixedBugs = NumberPrototype.toFixed && ( + (0.00008).toFixed(3) !== '0.000' || + (0.9).toFixed(0) !== '1' || + (1.255).toFixed(2) !== '1.25' || + (1000000000000000128).toFixed(0) !== '1000000000000000128' +); + +var toFixedHelpers = { + base: 1e7, + size: 6, + data: [0, 0, 0, 0, 0, 0], + multiply: function multiply(n, c) { + var i = -1; + var c2 = c; + while (++i < toFixedHelpers.size) { + c2 += n * toFixedHelpers.data[i]; + toFixedHelpers.data[i] = c2 % toFixedHelpers.base; + c2 = Math.floor(c2 / toFixedHelpers.base); + } + }, + divide: function divide(n) { + var i = toFixedHelpers.size, c = 0; + while (--i >= 0) { + c += toFixedHelpers.data[i]; + toFixedHelpers.data[i] = Math.floor(c / n); + c = (c % n) * toFixedHelpers.base; + } + }, + numToString: function numToString() { + var i = toFixedHelpers.size; + var s = ''; + while (--i >= 0) { + if (s !== '' || i === 0 || toFixedHelpers.data[i] !== 0) { + var t = $String(toFixedHelpers.data[i]); + if (s === '') { + s = t; + } else { + s += strSlice('0000000', 0, 7 - t.length) + t; + } + } + } + return s; + }, + pow: function pow(x, n, acc) { + return (n === 0 ? acc : (n % 2 === 1 ? pow(x, n - 1, acc * x) : pow(x * x, n / 2, acc))); + }, + log: function log(x) { + var n = 0; + var x2 = x; + while (x2 >= 4096) { + n += 12; + x2 /= 4096; + } + while (x2 >= 2) { + n += 1; + x2 /= 2; + } + return n; + } +}; + +var toFixedShim = function toFixed(fractionDigits) { + var f, x, s, m, e, z, j, k; + + // Test for NaN and round fractionDigits down + f = $Number(fractionDigits); + f = isActualNaN(f) ? 0 : Math.floor(f); + + if (f < 0 || f > 20) { + throw new RangeError('Number.toFixed called with invalid number of decimals'); + } + + x = $Number(this); + + if (isActualNaN(x)) { + return 'NaN'; + } + + // If it is too big or small, return the string value of the number + if (x <= -1e21 || x >= 1e21) { + return $String(x); + } + + s = ''; + + if (x < 0) { + s = '-'; + x = -x; + } + + m = '0'; + + if (x > 1e-21) { + // 1e-21 < x < 1e21 + // -70 < log2(x) < 70 + e = toFixedHelpers.log(x * toFixedHelpers.pow(2, 69, 1)) - 69; + z = (e < 0 ? x * toFixedHelpers.pow(2, -e, 1) : x / toFixedHelpers.pow(2, e, 1)); + z *= 0x10000000000000; // Math.pow(2, 52); + e = 52 - e; + + // -18 < e < 122 + // x = z / 2 ^ e + if (e > 0) { + toFixedHelpers.multiply(0, z); + j = f; + + while (j >= 7) { + toFixedHelpers.multiply(1e7, 0); + j -= 7; + } + + toFixedHelpers.multiply(toFixedHelpers.pow(10, j, 1), 0); + j = e - 1; + + while (j >= 23) { + toFixedHelpers.divide(1 << 23); + j -= 23; + } + + toFixedHelpers.divide(1 << j); + toFixedHelpers.multiply(1, 1); + toFixedHelpers.divide(2); + m = toFixedHelpers.numToString(); + } else { + toFixedHelpers.multiply(0, z); + toFixedHelpers.multiply(1 << (-e), 0); + m = toFixedHelpers.numToString() + strSlice('0.00000000000000000000', 2, 2 + f); + } + } + + if (f > 0) { + k = m.length; + + if (k <= f) { + m = s + strSlice('0.0000000000000000000', 0, f - k + 2) + m; + } else { + m = s + strSlice(m, 0, k - f) + '.' + strSlice(m, k - f); + } + } else { + m = s + m; + } + + return m; +}; +defineProperties(NumberPrototype, { toFixed: toFixedShim }, hasToFixedBugs); + +var hasToPrecisionUndefinedBug = (function () { + try { + return 1.0.toPrecision(undefined) === '1'; + } catch (e) { + return true; + } +}()); +var originalToPrecision = NumberPrototype.toPrecision; +defineProperties(NumberPrototype, { + toPrecision: function toPrecision(precision) { + return typeof precision === 'undefined' ? originalToPrecision.call(this) : originalToPrecision.call(this, precision); + } +}, hasToPrecisionUndefinedBug); + +// +// String +// ====== +// + +// ES5 15.5.4.14 +// http://es5.github.com/#x15.5.4.14 + +// [bugfix, IE lt 9, firefox 4, Konqueror, Opera, obscure browsers] +// Many browsers do not split properly with regular expressions or they +// do not perform the split correctly under obscure conditions. +// See http://blog.stevenlevithan.com/archives/cross-browser-split +// I've tested in many browsers and this seems to cover the deviant ones: +// 'ab'.split(/(?:ab)*/) should be ["", ""], not [""] +// '.'.split(/(.?)(.?)/) should be ["", ".", "", ""], not ["", ""] +// 'tesst'.split(/(s)*/) should be ["t", undefined, "e", "s", "t"], not +// [undefined, "t", undefined, "e", ...] +// ''.split(/.?/) should be [], not [""] +// '.'.split(/()()/) should be ["."], not ["", "", "."] + +if ( + 'ab'.split(/(?:ab)*/).length !== 2 || + '.'.split(/(.?)(.?)/).length !== 4 || + 'tesst'.split(/(s)*/)[1] === 't' || + 'test'.split(/(?:)/, -1).length !== 4 || + ''.split(/.?/).length || + '.'.split(/()()/).length > 1 +) { + (function () { + var compliantExecNpcg = typeof (/()??/).exec('')[1] === 'undefined'; // NPCG: nonparticipating capturing group + var maxSafe32BitInt = Math.pow(2, 32) - 1; + + StringPrototype.split = function (separator, limit) { + var string = String(this); + if (typeof separator === 'undefined' && limit === 0) { + return []; + } + + // If `separator` is not a regex, use native split + if (!isRegex(separator)) { + return strSplit(this, separator, limit); + } + + var output = []; + var flags = (separator.ignoreCase ? 'i' : '') + + (separator.multiline ? 'm' : '') + + (separator.unicode ? 'u' : '') + // in ES6 + (separator.sticky ? 'y' : ''), // Firefox 3+ and ES6 + lastLastIndex = 0, + // Make `global` and avoid `lastIndex` issues by working with a copy + separator2, match, lastIndex, lastLength; + var separatorCopy = new RegExp(separator.source, flags + 'g'); + if (!compliantExecNpcg) { + // Doesn't need flags gy, but they don't hurt + separator2 = new RegExp('^' + separatorCopy.source + '$(?!\\s)', flags); + } + /* Values for `limit`, per the spec: + * If undefined: 4294967295 // maxSafe32BitInt + * If 0, Infinity, or NaN: 0 + * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296; + * If negative number: 4294967296 - Math.floor(Math.abs(limit)) + * If other: Type-convert, then use the above rules + */ + var splitLimit = typeof limit === 'undefined' ? maxSafe32BitInt : ES.ToUint32(limit); + match = separatorCopy.exec(string); + while (match) { + // `separatorCopy.lastIndex` is not reliable cross-browser + lastIndex = match.index + match[0].length; + if (lastIndex > lastLastIndex) { + pushCall(output, strSlice(string, lastLastIndex, match.index)); + // Fix browsers whose `exec` methods don't consistently return `undefined` for + // nonparticipating capturing groups + if (!compliantExecNpcg && match.length > 1) { + /* eslint-disable no-loop-func */ + match[0].replace(separator2, function () { + for (var i = 1; i < arguments.length - 2; i++) { + if (typeof arguments[i] === 'undefined') { + match[i] = void 0; + } + } + }); + /* eslint-enable no-loop-func */ + } + if (match.length > 1 && match.index < string.length) { + array_push.apply(output, arraySlice(match, 1)); + } + lastLength = match[0].length; + lastLastIndex = lastIndex; + if (output.length >= splitLimit) { + break; + } + } + if (separatorCopy.lastIndex === match.index) { + separatorCopy.lastIndex++; // Avoid an infinite loop + } + match = separatorCopy.exec(string); + } + if (lastLastIndex === string.length) { + if (lastLength || !separatorCopy.test('')) { + pushCall(output, ''); + } + } else { + pushCall(output, strSlice(string, lastLastIndex)); + } + return output.length > splitLimit ? strSlice(output, 0, splitLimit) : output; + }; + }()); + +// [bugfix, chrome] +// If separator is undefined, then the result array contains just one String, +// which is the this value (converted to a String). If limit is not undefined, +// then the output array is truncated so that it contains no more than limit +// elements. +// "0".split(undefined, 0) -> [] +} else if ('0'.split(void 0, 0).length) { + StringPrototype.split = function split(separator, limit) { + if (typeof separator === 'undefined' && limit === 0) { return []; } + return strSplit(this, separator, limit); + }; +} + +var str_replace = StringPrototype.replace; +var replaceReportsGroupsCorrectly = (function () { + var groups = []; + 'x'.replace(/x(.)?/g, function (match, group) { + pushCall(groups, group); + }); + return groups.length === 1 && typeof groups[0] === 'undefined'; +}()); + +if (!replaceReportsGroupsCorrectly) { + StringPrototype.replace = function replace(searchValue, replaceValue) { + var isFn = isCallable(replaceValue); + var hasCapturingGroups = isRegex(searchValue) && (/\)[*?]/).test(searchValue.source); + if (!isFn || !hasCapturingGroups) { + return str_replace.call(this, searchValue, replaceValue); + } else { + var wrappedReplaceValue = function (match) { + var length = arguments.length; + var originalLastIndex = searchValue.lastIndex; + searchValue.lastIndex = 0; + var args = searchValue.exec(match) || []; + searchValue.lastIndex = originalLastIndex; + pushCall(args, arguments[length - 2], arguments[length - 1]); + return replaceValue.apply(this, args); + }; + return str_replace.call(this, searchValue, wrappedReplaceValue); + } + }; +} + +// ECMA-262, 3rd B.2.3 +// Not an ECMAScript standard, although ECMAScript 3rd Edition has a +// non-normative section suggesting uniform semantics and it should be +// normalized across all browsers +// [bugfix, IE lt 9] IE < 9 substr() with negative value not working in IE +var string_substr = StringPrototype.substr; +var hasNegativeSubstrBug = ''.substr && '0b'.substr(-1) !== 'b'; +defineProperties(StringPrototype, { + substr: function substr(start, length) { + var normalizedStart = start; + if (start < 0) { + normalizedStart = max(this.length + start, 0); + } + return string_substr.call(this, normalizedStart, length); + } +}, hasNegativeSubstrBug); + +// ES5 15.5.4.20 +// whitespace from: http://es5.github.io/#x15.5.4.20 +var ws = '\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003' + + '\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028' + + '\u2029\uFEFF'; +var zeroWidth = '\u200b'; +var wsRegexChars = '[' + ws + ']'; +var trimBeginRegexp = new RegExp('^' + wsRegexChars + wsRegexChars + '*'); +var trimEndRegexp = new RegExp(wsRegexChars + wsRegexChars + '*$'); +var hasTrimWhitespaceBug = StringPrototype.trim && (ws.trim() || !zeroWidth.trim()); +defineProperties(StringPrototype, { + // http://blog.stevenlevithan.com/archives/faster-trim-javascript + // http://perfectionkills.com/whitespace-deviations/ + trim: function trim() { + if (typeof this === 'undefined' || this === null) { + throw new TypeError("can't convert " + this + ' to object'); + } + return $String(this).replace(trimBeginRegexp, '').replace(trimEndRegexp, ''); + } +}, hasTrimWhitespaceBug); +var trim = call.bind(String.prototype.trim); + +var hasLastIndexBug = StringPrototype.lastIndexOf && 'abcあい'.lastIndexOf('あい', 2) !== -1; +defineProperties(StringPrototype, { + lastIndexOf: function lastIndexOf(searchString) { + if (typeof this === 'undefined' || this === null) { + throw new TypeError("can't convert " + this + ' to object'); + } + var S = $String(this); + var searchStr = $String(searchString); + var numPos = arguments.length > 1 ? $Number(arguments[1]) : NaN; + var pos = isActualNaN(numPos) ? Infinity : ES.ToInteger(numPos); + var start = min(max(pos, 0), S.length); + var searchLen = searchStr.length; + var k = start + searchLen; + while (k > 0) { + k = max(0, k - searchLen); + var index = strIndexOf(strSlice(S, k, start + searchLen), searchStr); + if (index !== -1) { + return k + index; + } + } + return -1; + } +}, hasLastIndexBug); + +var originalLastIndexOf = StringPrototype.lastIndexOf; +defineProperties(StringPrototype, { + lastIndexOf: function lastIndexOf(searchString) { + return originalLastIndexOf.apply(this, arguments); + } +}, StringPrototype.lastIndexOf.length !== 1); + +// ES-5 15.1.2.2 +/* eslint-disable radix */ +if (parseInt(ws + '08') !== 8 || parseInt(ws + '0x16') !== 22) { +/* eslint-enable radix */ + /* global parseInt: true */ + parseInt = (function (origParseInt) { + var hexRegex = /^[\-+]?0[xX]/; + return function parseInt(str, radix) { + var string = trim(str); + var defaultedRadix = $Number(radix) || (hexRegex.test(string) ? 16 : 10); + return origParseInt(string, defaultedRadix); + }; + }(parseInt)); +} + +// https://es5.github.io/#x15.1.2.3 +if (1 / parseFloat('-0') !== -Infinity) { + /* global parseFloat: true */ + parseFloat = (function (origParseFloat) { + return function parseFloat(string) { + var inputString = trim(string); + var result = origParseFloat(inputString); + return result === 0 && strSlice(inputString, 0, 1) === '-' ? -0 : result; + }; + }(parseFloat)); +} + +if (String(new RangeError('test')) !== 'RangeError: test') { + var errorToStringShim = function toString() { + if (typeof this === 'undefined' || this === null) { + throw new TypeError("can't convert " + this + ' to object'); + } + var name = this.name; + if (typeof name === 'undefined') { + name = 'Error'; + } else if (typeof name !== 'string') { + name = $String(name); + } + var msg = this.message; + if (typeof msg === 'undefined') { + msg = ''; + } else if (typeof msg !== 'string') { + msg = $String(msg); + } + if (!name) { + return msg; + } + if (!msg) { + return name; + } + return name + ': ' + msg; + }; + // can't use defineProperties here because of toString enumeration issue in IE <= 8 + Error.prototype.toString = errorToStringShim; +} + +if (supportsDescriptors) { + var ensureNonEnumerable = function (obj, prop) { + if (isEnum(obj, prop)) { + var desc = Object.getOwnPropertyDescriptor(obj, prop); + desc.enumerable = false; + Object.defineProperty(obj, prop, desc); + } + }; + ensureNonEnumerable(Error.prototype, 'message'); + if (Error.prototype.message !== '') { + Error.prototype.message = ''; + } + ensureNonEnumerable(Error.prototype, 'name'); +} + +if (String(/a/mig) !== '/a/gim') { + var regexToString = function toString() { + var str = '/' + this.source + '/'; + if (this.global) { + str += 'g'; + } + if (this.ignoreCase) { + str += 'i'; + } + if (this.multiline) { + str += 'm'; + } + return str; + }; + // can't use defineProperties here because of toString enumeration issue in IE <= 8 + RegExp.prototype.toString = regexToString; +} + +})); + +/*! + * https://github.com/es-shims/es5-shim + * @license es5-shim Copyright 2009-2015 by contributors, MIT License + * see https://github.com/es-shims/es5-shim/blob/master/LICENSE + */ + +// vim: ts=4 sts=4 sw=4 expandtab + +// Add semicolon to prevent IIFE from being passed as argument to concatenated code. +; + +// UMD (Universal Module Definition) +// see https://github.com/umdjs/umd/blob/master/templates/returnExports.js +(function (root, factory) { + 'use strict'; + + /* global define, exports, module */ + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(factory); + } else if (typeof exports === 'object') { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like enviroments that support module.exports, + // like Node. + module.exports = factory(); + } else { + // Browser globals (root is window) + root.returnExports = factory(); + } +}(this, function () { + +var call = Function.call; +var prototypeOfObject = Object.prototype; +var owns = call.bind(prototypeOfObject.hasOwnProperty); +var isEnumerable = call.bind(prototypeOfObject.propertyIsEnumerable); +var toStr = call.bind(prototypeOfObject.toString); + +// If JS engine supports accessors creating shortcuts. +var defineGetter; +var defineSetter; +var lookupGetter; +var lookupSetter; +var supportsAccessors = owns(prototypeOfObject, '__defineGetter__'); +if (supportsAccessors) { + /* eslint-disable no-underscore-dangle */ + defineGetter = call.bind(prototypeOfObject.__defineGetter__); + defineSetter = call.bind(prototypeOfObject.__defineSetter__); + lookupGetter = call.bind(prototypeOfObject.__lookupGetter__); + lookupSetter = call.bind(prototypeOfObject.__lookupSetter__); + /* eslint-enable no-underscore-dangle */ +} + +// ES5 15.2.3.2 +// http://es5.github.com/#x15.2.3.2 +if (!Object.getPrototypeOf) { + // https://github.com/es-shims/es5-shim/issues#issue/2 + // http://ejohn.org/blog/objectgetprototypeof/ + // recommended by fschaefer on github + // + // sure, and webreflection says ^_^ + // ... this will nerever possibly return null + // ... Opera Mini breaks here with infinite loops + Object.getPrototypeOf = function getPrototypeOf(object) { + /* eslint-disable no-proto */ + var proto = object.__proto__; + /* eslint-enable no-proto */ + if (proto || proto === null) { + return proto; + } else if (toStr(object.constructor) === '[object Function]') { + return object.constructor.prototype; + } else if (object instanceof Object) { + return prototypeOfObject; + } else { + // Correctly return null for Objects created with `Object.create(null)` + // (shammed or native) or `{ __proto__: null}`. Also returns null for + // cross-realm objects on browsers that lack `__proto__` support (like + // IE <11), but that's the best we can do. + return null; + } + }; +} + +// ES5 15.2.3.3 +// http://es5.github.com/#x15.2.3.3 + +var doesGetOwnPropertyDescriptorWork = function doesGetOwnPropertyDescriptorWork(object) { + try { + object.sentinel = 0; + return Object.getOwnPropertyDescriptor(object, 'sentinel').value === 0; + } catch (exception) { + return false; + } +}; + +// check whether getOwnPropertyDescriptor works if it's given. Otherwise, shim partially. +if (Object.defineProperty) { + var getOwnPropertyDescriptorWorksOnObject = doesGetOwnPropertyDescriptorWork({}); + var getOwnPropertyDescriptorWorksOnDom = typeof document === 'undefined' || + doesGetOwnPropertyDescriptorWork(document.createElement('div')); + if (!getOwnPropertyDescriptorWorksOnDom || !getOwnPropertyDescriptorWorksOnObject) { + var getOwnPropertyDescriptorFallback = Object.getOwnPropertyDescriptor; + } +} + +if (!Object.getOwnPropertyDescriptor || getOwnPropertyDescriptorFallback) { + var ERR_NON_OBJECT = 'Object.getOwnPropertyDescriptor called on a non-object: '; + + /* eslint-disable no-proto */ + Object.getOwnPropertyDescriptor = function getOwnPropertyDescriptor(object, property) { + if ((typeof object !== 'object' && typeof object !== 'function') || object === null) { + throw new TypeError(ERR_NON_OBJECT + object); + } + + // make a valiant attempt to use the real getOwnPropertyDescriptor + // for I8's DOM elements. + if (getOwnPropertyDescriptorFallback) { + try { + return getOwnPropertyDescriptorFallback.call(Object, object, property); + } catch (exception) { + // try the shim if the real one doesn't work + } + } + + var descriptor; + + // If object does not owns property return undefined immediately. + if (!owns(object, property)) { + return descriptor; + } + + // If object has a property then it's for sure `configurable`, and + // probably `enumerable`. Detect enumerability though. + descriptor = { + enumerable: isEnumerable(object, property), + configurable: true + }; + + // If JS engine supports accessor properties then property may be a + // getter or setter. + if (supportsAccessors) { + // Unfortunately `__lookupGetter__` will return a getter even + // if object has own non getter property along with a same named + // inherited getter. To avoid misbehavior we temporary remove + // `__proto__` so that `__lookupGetter__` will return getter only + // if it's owned by an object. + var prototype = object.__proto__; + var notPrototypeOfObject = object !== prototypeOfObject; + // avoid recursion problem, breaking in Opera Mini when + // Object.getOwnPropertyDescriptor(Object.prototype, 'toString') + // or any other Object.prototype accessor + if (notPrototypeOfObject) { + object.__proto__ = prototypeOfObject; + } + + var getter = lookupGetter(object, property); + var setter = lookupSetter(object, property); + + if (notPrototypeOfObject) { + // Once we have getter and setter we can put values back. + object.__proto__ = prototype; + } + + if (getter || setter) { + if (getter) { + descriptor.get = getter; + } + if (setter) { + descriptor.set = setter; + } + // If it was accessor property we're done and return here + // in order to avoid adding `value` to the descriptor. + return descriptor; + } + } + + // If we got this far we know that object has an own property that is + // not an accessor so we set it as a value and return descriptor. + descriptor.value = object[property]; + descriptor.writable = true; + return descriptor; + }; + /* eslint-enable no-proto */ +} + +// ES5 15.2.3.4 +// http://es5.github.com/#x15.2.3.4 +if (!Object.getOwnPropertyNames) { + Object.getOwnPropertyNames = function getOwnPropertyNames(object) { + return Object.keys(object); + }; +} + +// ES5 15.2.3.5 +// http://es5.github.com/#x15.2.3.5 +if (!Object.create) { + + // Contributed by Brandon Benvie, October, 2012 + var createEmpty; + var supportsProto = !({ __proto__: null } instanceof Object); + // the following produces false positives + // in Opera Mini => not a reliable check + // Object.prototype.__proto__ === null + + // Check for document.domain and active x support + // No need to use active x approach when document.domain is not set + // see https://github.com/es-shims/es5-shim/issues/150 + // variation of https://github.com/kitcambridge/es5-shim/commit/4f738ac066346 + /* global ActiveXObject */ + var shouldUseActiveX = function shouldUseActiveX() { + // return early if document.domain not set + if (!document.domain) { + return false; + } + + try { + return !!new ActiveXObject('htmlfile'); + } catch (exception) { + return false; + } + }; + + // This supports IE8 when document.domain is used + // see https://github.com/es-shims/es5-shim/issues/150 + // variation of https://github.com/kitcambridge/es5-shim/commit/4f738ac066346 + var getEmptyViaActiveX = function getEmptyViaActiveX() { + var empty; + var xDoc; + + xDoc = new ActiveXObject('htmlfile'); + + xDoc.write('"),b.close(),a=b.parentWindow.Object.prototype,b=null,a},u=function(){var a,c=b.createElement("iframe"),d=b.body||b.documentElement;return c.style.display="none",d.appendChild(c),c.src="javascript:",a=c.contentWindow.Object.prototype,d.removeChild(c),c=null,a};q=r||"undefined"==typeof b?function(){return{__proto__:null}}:function(){var a=s()?t():u();delete a.constructor,delete a.hasOwnProperty,delete a.propertyIsEnumerable,delete a.isPrototypeOf,delete a.toLocaleString,delete a.toString,delete a.valueOf;var b=function(){};return b.prototype=a,q=function(){return new b},new b},Object.create=function(a,b){var c,d=function(){};if(null===a)c=q();else{if("object"!=typeof a&&"function"!=typeof a)throw new TypeError("Object prototype may only be an Object or null");d.prototype=a,c=new d,c.__proto__=a}return void 0!==b&&Object.defineProperties(c,b),c}}var v=function(a){try{return Object.defineProperty(a,"sentinel",{}),"sentinel"in a}catch(b){return!1}};if(Object.defineProperty){var w=v({}),x="undefined"==typeof b||v(b.createElement("div"));if(!w||!x)var y=Object.defineProperty,z=Object.defineProperties}if(!Object.defineProperty||y){var A="Property description must be an object: ",B="Object.defineProperty called on non-object: ",C="getters & setters can not be defined on this javascript engine";Object.defineProperty=function(b,f,h){if("object"!=typeof b&&"function"!=typeof b||null===b)throw new TypeError(B+b);if("object"!=typeof h&&"function"!=typeof h||null===h)throw new TypeError(A+h);if(y)try{return y.call(Object,b,f,h)}catch(i){}if("value"in h)if(k&&(d(b,f)||e(b,f))){var j=b.__proto__;b.__proto__=g,delete b[f],b[f]=h.value,b.__proto__=j}else b[f]=h.value;else{if(!k&&("get"in h||"set"in h))throw new TypeError(C);"get"in h&&a(b,f,h.get),"set"in h&&c(b,f,h.set)}return b}}(!Object.defineProperties||z)&&(Object.defineProperties=function(a,b){if(z)try{return z.call(Object,a,b)}catch(c){}return Object.keys(b).forEach(function(c){"__proto__"!==c&&Object.defineProperty(a,c,b[c])}),a}),Object.seal||(Object.seal=function(a){if(Object(a)!==a)throw new TypeError("Object.seal can only be called on Objects.");return a}),Object.freeze||(Object.freeze=function(a){if(Object(a)!==a)throw new TypeError("Object.freeze can only be called on Objects.");return a});try{Object.freeze(function(){})}catch(D){Object.freeze=function(a){return function(b){return"function"==typeof b?b:a(b)}}(Object.freeze)}Object.preventExtensions||(Object.preventExtensions=function(a){if(Object(a)!==a)throw new TypeError("Object.preventExtensions can only be called on Objects.");return a}),Object.isSealed||(Object.isSealed=function(a){if(Object(a)!==a)throw new TypeError("Object.isSealed can only be called on Objects.");return!1}),Object.isFrozen||(Object.isFrozen=function(a){if(Object(a)!==a)throw new TypeError("Object.isFrozen can only be called on Objects.");return!1}),Object.isExtensible||(Object.isExtensible=function(a){if(Object(a)!==a)throw new TypeError("Object.isExtensible can only be called on Objects.");for(var b="";h(a,b);)b+="?";a[b]=!0;var c=h(a,b);return delete a[b],c})})}(window,document); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/ar.js b/vendor/assets/javascripts/lang/ar.js new file mode 100644 index 0000000..4c49d62 --- /dev/null +++ b/vendor/assets/javascripts/lang/ar.js @@ -0,0 +1,34 @@ +videojs.addLanguage("ar",{ + "Play": "تشغيل", + "Pause": "ايقاف", + "Current Time": "الوقت الحالي", + "Duration Time": "Dauer", + "Remaining Time": "الوقت المتبقي", + "Stream Type": "نوع التيار", + "LIVE": "مباشر", + "Loaded": "تم التحميل", + "Progress": "التقدم", + "Fullscreen": "ملء الشاشة", + "Non-Fullscreen": "غير ملء الشاشة", + "Mute": "صامت", + "Unmute": "غير الصامت", + "Playback Rate": "معدل التشغيل", + "Subtitles": "الترجمة", + "subtitles off": "ايقاف الترجمة", + "Captions": "التعليقات", + "captions off": "ايقاف التعليقات", + "Chapters": "فصول", + "You aborted the media playback": "لقد ألغيت تشغيل الفيديو", + "A network error caused the media download to fail part-way.": "تسبب خطأ في الشبكة بفشل تحميل الفيديو بالكامل.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "لا يمكن تحميل الفيديو بسبب فشل في الخادم أو الشبكة ، أو فشل بسبب عدم امكانية قراءة تنسيق الفيديو.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "تم ايقاف تشغيل الفيديو بسبب مشكلة فساد أو لأن الفيديو المستخدم يستخدم ميزات غير مدعومة من متصفحك.", + "No compatible source was found for this media.": "فشل العثور على أي مصدر متوافق مع هذا الفيديو.", + "Play Video": "تشغيل الفيديو", + "Close": "أغلق", + "Modal Window": "نافذة مشروطة", + "This is a modal window": "هذه نافذة مشروطة", + "This modal can be closed by pressing the Escape key or activating the close button.": "يمكن غلق هذه النافذة المشروطة عن طريق الضغط على زر الخروج أو تفعيل زر الإغلاق", + ", opens captions settings dialog": ", تفتح نافذة خيارات التعليقات", + ", opens subtitles settings dialog": ", تفتح نافذة خيارات الترجمة", + ", selected": ", مختار" +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/ba.js b/vendor/assets/javascripts/lang/ba.js new file mode 100644 index 0000000..b7ca3da --- /dev/null +++ b/vendor/assets/javascripts/lang/ba.js @@ -0,0 +1,26 @@ +videojs.addLanguage("ba",{ + "Play": "Pusti", + "Pause": "Pauza", + "Current Time": "Trenutno vrijeme", + "Duration Time": "Vrijeme trajanja", + "Remaining Time": "Preostalo vrijeme", + "Stream Type": "Način strimovanja", + "LIVE": "UŽIVO", + "Loaded": "Učitan", + "Progress": "Progres", + "Fullscreen": "Puni ekran", + "Non-Fullscreen": "Mali ekran", + "Mute": "Prigušen", + "Unmute": "Ne-prigušen", + "Playback Rate": "Stopa reprodukcije", + "Subtitles": "Podnaslov", + "subtitles off": "Podnaslov deaktiviran", + "Captions": "Titlovi", + "captions off": "Titlovi deaktivirani", + "Chapters": "Poglavlja", + "You aborted the media playback": "Isključili ste reprodukciju videa.", + "A network error caused the media download to fail part-way.": "Video se prestao preuzimati zbog greške na mreži.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "Video se ne može reproducirati zbog servera, greške u mreži ili je format ne podržan.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Reprodukcija videa je zaustavljenja zbog greške u formatu ili zbog verzije vašeg pretraživača.", + "No compatible source was found for this media.": "Nije nađen nijedan kompatibilan izvor ovog videa." +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/bg.js b/vendor/assets/javascripts/lang/bg.js new file mode 100644 index 0000000..34e48ec --- /dev/null +++ b/vendor/assets/javascripts/lang/bg.js @@ -0,0 +1,26 @@ +videojs.addLanguage("bg",{ + "Play": "Възпроизвеждане", + "Pause": "Пауза", + "Current Time": "Текущо време", + "Duration Time": "Продължителност", + "Remaining Time": "Оставащо време", + "Stream Type": "Тип на потока", + "LIVE": "НА ЖИВО", + "Loaded": "Заредено", + "Progress": "Прогрес", + "Fullscreen": "Цял екран", + "Non-Fullscreen": "Спиране на цял екран", + "Mute": "Без звук", + "Unmute": "Със звук", + "Playback Rate": "Скорост на възпроизвеждане", + "Subtitles": "Субтитри", + "subtitles off": "Спряни субтитри", + "Captions": "Аудио надписи", + "captions off": "Спряни аудио надписи", + "Chapters": "Глави", + "You aborted the media playback": "Спряхте възпроизвеждането на видеото", + "A network error caused the media download to fail part-way.": "Грешка в мрежата провали изтеглянето на видеото.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "Видеото не може да бъде заредено заради проблем със сървъра или мрежата или защото този формат не е поддържан.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Възпроизвеждането на видеото беше прекъснато заради проблем с файла или защото видеото използва опции които браузърът Ви не поддържа.", + "No compatible source was found for this media.": "Не беше намерен съвместим източник за това видео." +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/ca.js b/vendor/assets/javascripts/lang/ca.js new file mode 100644 index 0000000..03371ee --- /dev/null +++ b/vendor/assets/javascripts/lang/ca.js @@ -0,0 +1,26 @@ +videojs.addLanguage("ca",{ + "Play": "Reproducció", + "Pause": "Pausa", + "Current Time": "Temps reproduït", + "Duration Time": "Durada total", + "Remaining Time": "Temps restant", + "Stream Type": "Tipus de seqüència", + "LIVE": "EN DIRECTE", + "Loaded": "Carregat", + "Progress": "Progrés", + "Fullscreen": "Pantalla completa", + "Non-Fullscreen": "Pantalla no completa", + "Mute": "Silencia", + "Unmute": "Amb so", + "Playback Rate": "Velocitat de reproducció", + "Subtitles": "Subtítols", + "subtitles off": "Subtítols desactivats", + "Captions": "Llegendes", + "captions off": "Llegendes desactivades", + "Chapters": "Capítols", + "You aborted the media playback": "Heu interromput la reproducció del vídeo.", + "A network error caused the media download to fail part-way.": "Un error de la xarxa ha interromput la baixada del vídeo.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "No s'ha pogut carregar el vídeo perquè el servidor o la xarxa han fallat, o bé perquè el seu format no és compatible.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "La reproducció de vídeo s'ha interrumput per un problema de corrupció de dades o bé perquè el vídeo demanava funcions que el vostre navegador no ofereix.", + "No compatible source was found for this media.": "No s'ha trobat cap font compatible amb el vídeo." +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/cs.js b/vendor/assets/javascripts/lang/cs.js new file mode 100644 index 0000000..f6004aa --- /dev/null +++ b/vendor/assets/javascripts/lang/cs.js @@ -0,0 +1,26 @@ +videojs.addLanguage("cs",{ + "Play": "Přehrát", + "Pause": "Pauza", + "Current Time": "Aktuální čas", + "Duration Time": "Doba trvání", + "Remaining Time": "Zbývající čas", + "Stream Type": "Stream Type", + "LIVE": "ŽIVĚ", + "Loaded": "Načteno", + "Progress": "Stav", + "Fullscreen": "Celá obrazovka", + "Non-Fullscreen": "Zmenšená obrazovka", + "Mute": "Ztlumit zvuk", + "Unmute": "Přehrát zvuk", + "Playback Rate": "Rychlost přehrávání", + "Subtitles": "Titulky", + "subtitles off": "Titulky vypnuty", + "Captions": "Popisky", + "captions off": "Popisky vypnuty", + "Chapters": "Kapitoly", + "You aborted the media playback": "Přehrávání videa je přerušeno.", + "A network error caused the media download to fail part-way.": "Video nemohlo být načteno, kvůli chybě v síti.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "Video nemohlo být načteno, buď kvůli chybě serveru nebo sítě nebo proto, že daný formát není podporován.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Váš prohlížeč nepodporuje formát videa.", + "No compatible source was found for this media.": "Špatně zadaný zdroj videa." +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/da.js b/vendor/assets/javascripts/lang/da.js new file mode 100644 index 0000000..f6b3ada --- /dev/null +++ b/vendor/assets/javascripts/lang/da.js @@ -0,0 +1,26 @@ +videojs.addLanguage("da",{ + "Play": "Afspil", + "Pause": "Pause", + "Current Time": "Aktuel tid", + "Duration Time": "Varighed", + "Remaining Time": "Resterende tid", + "Stream Type": "Stream-type", + "LIVE": "LIVE", + "Loaded": "Indlæst", + "Progress": "Status", + "Fullscreen": "Fuldskærm", + "Non-Fullscreen": "Luk fuldskærm", + "Mute": "Uden lyd", + "Unmute": "Med lyd", + "Playback Rate": "Afspilningsrate", + "Subtitles": "Undertekster", + "subtitles off": "Uden undertekster", + "Captions": "Undertekster for hørehæmmede", + "captions off": "Uden undertekster for hørehæmmede", + "Chapters": "Kapitler", + "You aborted the media playback": "Du afbrød videoafspilningen.", + "A network error caused the media download to fail part-way.": "En netværksfejl fik download af videoen til at fejle.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "Videoen kunne ikke indlæses, enten fordi serveren eller netværket fejlede, eller fordi formatet ikke er understøttet.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Videoafspilningen blev afbrudt på grund af ødelagte data eller fordi videoen benyttede faciliteter som din browser ikke understøtter.", + "No compatible source was found for this media.": "Fandt ikke en kompatibel kilde for denne media." +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/de.js b/vendor/assets/javascripts/lang/de.js new file mode 100644 index 0000000..6b2377a --- /dev/null +++ b/vendor/assets/javascripts/lang/de.js @@ -0,0 +1,34 @@ +videojs.addLanguage("de",{ + "Play": "Wiedergabe", + "Pause": "Pause", + "Current Time": "Aktueller Zeitpunkt", + "Duration Time": "Dauer", + "Remaining Time": "Verbleibende Zeit", + "Stream Type": "Streamtyp", + "LIVE": "LIVE", + "Loaded": "Geladen", + "Progress": "Status", + "Fullscreen": "Vollbild", + "Non-Fullscreen": "Kein Vollbild", + "Mute": "Ton aus", + "Unmute": "Ton ein", + "Playback Rate": "Wiedergabegeschwindigkeit", + "Subtitles": "Untertitel", + "subtitles off": "Untertitel aus", + "Captions": "Untertitel", + "captions off": "Untertitel aus", + "Chapters": "Kapitel", + "You aborted the media playback": "Sie haben die Videowiedergabe abgebrochen.", + "A network error caused the media download to fail part-way.": "Der Videodownload ist aufgrund eines Netzwerkfehlers fehlgeschlagen.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "Das Video konnte nicht geladen werden, da entweder ein Server- oder Netzwerkfehler auftrat oder das Format nicht unterstützt wird.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Die Videowiedergabe wurde entweder wegen eines Problems mit einem beschädigten Video oder wegen verwendeten Funktionen, die vom Browser nicht unterstützt werden, abgebrochen.", + "No compatible source was found for this media.": "Für dieses Video wurde keine kompatible Quelle gefunden.", + "Play Video": "Video abspielen", + "Close": "Schließen", + "Modal Window": "Modales Fenster", + "This is a modal window": "Dies ist ein modales Fenster", + "This modal can be closed by pressing the Escape key or activating the close button.": "Durch Drücken der Esc-Taste bzw. Betätigung der Schaltfläche \"Schließen\" wird dieses modale Fenster geschlossen.", + ", opens captions settings dialog": ", öffnet Einstellungen für Untertitel", + ", opens subtitles settings dialog": ", öffnet Einstellungen für Untertitel", + ", selected": " (ausgewählt)" +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/el.js b/vendor/assets/javascripts/lang/el.js new file mode 100644 index 0000000..e95433d --- /dev/null +++ b/vendor/assets/javascripts/lang/el.js @@ -0,0 +1,34 @@ +videojs.addLanguage("el",{ + "Play": "Aναπαραγωγή", + "Pause": "Παύση", + "Current Time": "Τρέχων χρόνος", + "Duration Time": "Συνολικός χρόνος", + "Remaining Time": "Υπολοιπόμενος χρόνος", + "Stream Type": "Τύπος ροής", + "LIVE": "ΖΩΝΤΑΝΑ", + "Loaded": "Φόρτωση επιτυχής", + "Progress": "Πρόοδος", + "Fullscreen": "Πλήρης οθόνη", + "Non-Fullscreen": "Έξοδος από πλήρη οθόνη", + "Mute": "Σίγαση", + "Unmute": "Kατάργηση σίγασης", + "Playback Rate": "Ρυθμός αναπαραγωγής", + "Subtitles": "Υπότιτλοι", + "subtitles off": "απόκρυψη υπότιτλων", + "Captions": "Λεζάντες", + "captions off": "απόκρυψη λεζάντων", + "Chapters": "Κεφάλαια", + "You aborted the media playback": "Ακυρώσατε την αναπαραγωγή", + "A network error caused the media download to fail part-way.": "Ένα σφάλμα δικτύου προκάλεσε την αποτυχία μεταφόρτωσης του αρχείου προς αναπαραγωγή.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "Το αρχείο προς αναπαραγωγή δεν ήταν δυνατό να φορτωθεί είτε γιατί υπήρξε σφάλμα στον διακομιστή ή το δίκτυο, είτε γιατί ο τύπος του αρχείου δεν υποστηρίζεται.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Η αναπαραγωγή ακυρώθηκε είτε λόγω κατεστραμμένου αρχείου, είτε γιατί το αρχείο απαιτεί λειτουργίες που δεν υποστηρίζονται από το πρόγραμμα περιήγησης που χρησιμοποιείτε.", + "No compatible source was found for this media.": "Δεν βρέθηκε συμβατή πηγή αναπαραγωγής για το συγκεκριμένο αρχείο.", + "Play video": "Αναπαραγωγή βίντεο", + "Close": "Κλείσιμο", + "Modal Window": "Aναδυόμενο παράθυρο", + "This is a modal window": "Το παρών είναι ένα αναδυόμενο παράθυρο", + "This modal can be closed by pressing the Escape key or activating the close button.": "Αυτό το παράθυρο μπορεί να εξαφανιστεί πατώντας το πλήκτρο Escape ή πατώντας το κουμπί κλεισίματος.", + ", opens captions settings dialog": ", εμφανίζει τις ρυθμίσεις για τις λεζάντες", + ", opens subtitles settings dialog": ", εμφανίζει τις ρυθμίσεις για τους υπότιτλους", + ", selected": ", επιλεγμένο" +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/en.js b/vendor/assets/javascripts/lang/en.js new file mode 100644 index 0000000..ad89675 --- /dev/null +++ b/vendor/assets/javascripts/lang/en.js @@ -0,0 +1,37 @@ +videojs.addLanguage("en",{ + "Play": "Play", + "Pause": "Pause", + "Current Time": "Current Time", + "Duration Time": "Duration Time", + "Remaining Time": "Remaining Time", + "Stream Type": "Stream Type", + "LIVE": "LIVE", + "Loaded": "Loaded", + "Progress": "Progress", + "Fullscreen": "Fullscreen", + "Non-Fullscreen": "Non-Fullscreen", + "Mute": "Mute", + "Unmute": "Unmute", + "Playback Rate": "Playback Rate", + "Subtitles": "Subtitles", + "subtitles off": "subtitles off", + "Captions": "Captions", + "captions off": "captions off", + "Chapters": "Chapters", + "Descriptions": "Descriptions", + "descriptions off": "descriptions off", + "You aborted the media playback": "You aborted the media playback", + "A network error caused the media download to fail part-way.": "A network error caused the media download to fail part-way.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "The media could not be loaded, either because the server or network failed or because the format is not supported.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.", + "No compatible source was found for this media.": "No compatible source was found for this media.", + "Play Video": "Play Video", + "Close": "Close", + "Modal Window": "Modal Window", + "This is a modal window": "This is a modal window", + "This modal can be closed by pressing the Escape key or activating the close button.": "This modal can be closed by pressing the Escape key or activating the close button.", + ", opens captions settings dialog": ", opens captions settings dialog", + ", opens subtitles settings dialog": ", opens subtitles settings dialog", + ", opens descriptions settings dialog": ", opens descriptions settings dialog", + ", selected": ", selected" +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/es.js b/vendor/assets/javascripts/lang/es.js new file mode 100644 index 0000000..4096973 --- /dev/null +++ b/vendor/assets/javascripts/lang/es.js @@ -0,0 +1,26 @@ +videojs.addLanguage("es",{ + "Play": "Reproducción", + "Pause": "Pausa", + "Current Time": "Tiempo reproducido", + "Duration Time": "Duración total", + "Remaining Time": "Tiempo restante", + "Stream Type": "Tipo de secuencia", + "LIVE": "DIRECTO", + "Loaded": "Cargado", + "Progress": "Progreso", + "Fullscreen": "Pantalla completa", + "Non-Fullscreen": "Pantalla no completa", + "Mute": "Silenciar", + "Unmute": "No silenciado", + "Playback Rate": "Velocidad de reproducción", + "Subtitles": "Subtítulos", + "subtitles off": "Subtítulos desactivados", + "Captions": "Subtítulos especiales", + "captions off": "Subtítulos especiales desactivados", + "Chapters": "Capítulos", + "You aborted the media playback": "Ha interrumpido la reproducción del vídeo.", + "A network error caused the media download to fail part-way.": "Un error de red ha interrumpido la descarga del vídeo.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "No se ha podido cargar el vídeo debido a un fallo de red o del servidor o porque el formato es incompatible.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "La reproducción de vídeo se ha interrumpido por un problema de corrupción de datos o porque el vídeo precisa funciones que su navegador no ofrece.", + "No compatible source was found for this media.": "No se ha encontrado ninguna fuente compatible con este vídeo." +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/fa.js b/vendor/assets/javascripts/lang/fa.js new file mode 100644 index 0000000..b2e818e --- /dev/null +++ b/vendor/assets/javascripts/lang/fa.js @@ -0,0 +1,26 @@ +videojs.addLanguage("fa",{ + "Play": "پخش", + "Pause": "وقفه", + "Current Time": "زمان کنونی", + "Duration Time": "مدت زمان", + "Remaining Time": "زمان باقیمانده", + "Stream Type": "نوع استریم", + "LIVE": "زنده", + "Loaded": "فراخوانی شده", + "Progress": "پیشرفت", + "Fullscreen": "تمام صفحه", + "Non-Fullscreen": "نمایش عادی", + "Mute": "بی صدا", + "Unmute": "بهمراه صدا", + "Playback Rate": "سرعت پخش", + "Subtitles": "زیرنویس", + "subtitles off": "بدون زیرنویس", + "Captions": "عنوان", + "captions off": "بدون عنوان", + "Chapters": "فصل", + "You aborted the media playback": "شما پخش را متوقف کردید.", + "A network error caused the media download to fail part-way.": "مشکل در دریافت ویدئو ...", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "فرمت پشتیبانی نمیشود یا خطایی روی داده است.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "مشکل در دریافت ویدئو ...", + "No compatible source was found for this media.": "هیچ ورودی ای برای این رسانه شناسایی نشد." +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/fi.js b/vendor/assets/javascripts/lang/fi.js new file mode 100644 index 0000000..157c3ac --- /dev/null +++ b/vendor/assets/javascripts/lang/fi.js @@ -0,0 +1,26 @@ +videojs.addLanguage("fi",{ + "Play": "Toisto", + "Pause": "Tauko", + "Current Time": "Tämänhetkinen aika", + "Duration Time": "Kokonaisaika", + "Remaining Time": "Jäljellä oleva aika", + "Stream Type": "Lähetystyyppi", + "LIVE": "LIVE", + "Loaded": "Ladattu", + "Progress": "Edistyminen", + "Fullscreen": "Koko näyttö", + "Non-Fullscreen": "Koko näyttö pois", + "Mute": "Ääni pois", + "Unmute": "Ääni päällä", + "Playback Rate": "Toistonopeus", + "Subtitles": "Tekstitys", + "subtitles off": "Tekstitys pois", + "Captions": "Tekstitys", + "captions off": "Tekstitys pois", + "Chapters": "Kappaleet", + "You aborted the media playback": "Olet keskeyttänyt videotoiston.", + "A network error caused the media download to fail part-way.": "Verkkovirhe keskeytti videon latauksen.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "Videon lataus ei onnistunut joko palvelin- tai verkkovirheestä tai väärästä formaatista johtuen.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Videon toisto keskeytyi, koska media on vaurioitunut tai käyttää käyttää toimintoja, joita selaimesi ei tue.", + "No compatible source was found for this media.": "Tälle videolle ei löytynyt yhteensopivaa lähdettä." +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/fr.js b/vendor/assets/javascripts/lang/fr.js new file mode 100644 index 0000000..ac59b95 --- /dev/null +++ b/vendor/assets/javascripts/lang/fr.js @@ -0,0 +1,26 @@ +videojs.addLanguage("fr",{ + "Play": "Lecture", + "Pause": "Pause", + "Current Time": "Temps actuel", + "Duration Time": "Durée", + "Remaining Time": "Temps restant", + "Stream Type": "Type de flux", + "LIVE": "EN DIRECT", + "Loaded": "Chargé", + "Progress": "Progression", + "Fullscreen": "Plein écran", + "Non-Fullscreen": "Fenêtré", + "Mute": "Sourdine", + "Unmute": "Son activé", + "Playback Rate": "Vitesse de lecture", + "Subtitles": "Sous-titres", + "subtitles off": "Sous-titres désactivés", + "Captions": "Sous-titres", + "captions off": "Sous-titres désactivés", + "Chapters": "Chapitres", + "You aborted the media playback": "Vous avez interrompu la lecture de la vidéo.", + "A network error caused the media download to fail part-way.": "Une erreur de réseau a interrompu le téléchargement de la vidéo.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "Cette vidéo n'a pas pu être chargée, soit parce que le serveur ou le réseau a échoué ou parce que le format n'est pas reconnu.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "La lecture de la vidéo a été interrompue à cause d'un problème de corruption ou parce que la vidéo utilise des fonctionnalités non prises en charge par votre navigateur.", + "No compatible source was found for this media.": "Aucune source compatible n'a été trouvée pour cette vidéo." +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/hr.js b/vendor/assets/javascripts/lang/hr.js new file mode 100644 index 0000000..5e32a22 --- /dev/null +++ b/vendor/assets/javascripts/lang/hr.js @@ -0,0 +1,26 @@ +videojs.addLanguage("hr",{ + "Play": "Pusti", + "Pause": "Pauza", + "Current Time": "Trenutno vrijeme", + "Duration Time": "Vrijeme trajanja", + "Remaining Time": "Preostalo vrijeme", + "Stream Type": "Način strimovanja", + "LIVE": "UŽIVO", + "Loaded": "Učitan", + "Progress": "Progres", + "Fullscreen": "Puni ekran", + "Non-Fullscreen": "Mali ekran", + "Mute": "Prigušen", + "Unmute": "Ne-prigušen", + "Playback Rate": "Stopa reprodukcije", + "Subtitles": "Podnaslov", + "subtitles off": "Podnaslov deaktiviran", + "Captions": "Titlovi", + "captions off": "Titlovi deaktivirani", + "Chapters": "Poglavlja", + "You aborted the media playback": "Isključili ste reprodukciju videa.", + "A network error caused the media download to fail part-way.": "Video se prestao preuzimati zbog greške na mreži.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "Video se ne može reproducirati zbog servera, greške u mreži ili je format ne podržan.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Reprodukcija videa je zaustavljenja zbog greške u formatu ili zbog verzije vašeg pretraživača.", + "No compatible source was found for this media.": "Nije nađen nijedan kompatibilan izvor ovog videa." +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/hu.js b/vendor/assets/javascripts/lang/hu.js new file mode 100644 index 0000000..5b5123d --- /dev/null +++ b/vendor/assets/javascripts/lang/hu.js @@ -0,0 +1,26 @@ +videojs.addLanguage("hu",{ + "Play": "Lejátszás", + "Pause": "Szünet", + "Current Time": "Aktuális időpont", + "Duration Time": "Hossz", + "Remaining Time": "Hátralévő idő", + "Stream Type": "Adatfolyam típusa", + "LIVE": "ÉLŐ", + "Loaded": "Betöltve", + "Progress": "Állapot", + "Fullscreen": "Teljes képernyő", + "Non-Fullscreen": "Normál méret", + "Mute": "Némítás", + "Unmute": "Némítás kikapcsolva", + "Playback Rate": "Lejátszási sebesség", + "Subtitles": "Feliratok", + "subtitles off": "Feliratok kikapcsolva", + "Captions": "Magyarázó szöveg", + "captions off": "Magyarázó szöveg kikapcsolva", + "Chapters": "Fejezetek", + "You aborted the media playback": "Leállította a lejátszást", + "A network error caused the media download to fail part-way.": "Hálózati hiba miatt a videó részlegesen töltődött le.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "A videó nem tölthető be hálózati vagy kiszolgálói hiba miatt, vagy a formátuma nem támogatott.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "A lejátszás adatsérülés miatt leállt, vagy a videó egyes tulajdonságait a böngészője nem támogatja.", + "No compatible source was found for this media.": "Nincs kompatibilis forrás ehhez a videóhoz." +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/it.js b/vendor/assets/javascripts/lang/it.js new file mode 100644 index 0000000..1f7394b --- /dev/null +++ b/vendor/assets/javascripts/lang/it.js @@ -0,0 +1,26 @@ +videojs.addLanguage("it",{ + "Play": "Play", + "Pause": "Pausa", + "Current Time": "Orario attuale", + "Duration Time": "Durata", + "Remaining Time": "Tempo rimanente", + "Stream Type": "Tipo del Streaming", + "LIVE": "LIVE", + "Loaded": "Caricato", + "Progress": "Stato", + "Fullscreen": "Schermo intero", + "Non-Fullscreen": "Chiudi schermo intero", + "Mute": "Muto", + "Unmute": "Audio", + "Playback Rate": "Tasso di riproduzione", + "Subtitles": "Sottotitoli", + "subtitles off": "Senza sottotitoli", + "Captions": "Sottotitoli non udenti", + "captions off": "Senza sottotitoli non udenti", + "Chapters": "Capitolo", + "You aborted the media playback": "La riproduzione del filmato è stata interrotta.", + "A network error caused the media download to fail part-way.": "Il download del filmato è stato interrotto a causa di un problema rete.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "Il filmato non può essere caricato a causa di un errore nel server o nella rete o perché il formato non viene supportato.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "La riproduzione del filmato è stata interrotta a causa di un file danneggiato o per l’utilizzo di impostazioni non supportate dal browser.", + "No compatible source was found for this media.": "Non ci sono fonti compatibili per questo filmato." +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/ja.js b/vendor/assets/javascripts/lang/ja.js new file mode 100644 index 0000000..6c853b5 --- /dev/null +++ b/vendor/assets/javascripts/lang/ja.js @@ -0,0 +1,26 @@ +videojs.addLanguage("ja",{ + "Play": "再生", + "Pause": "一時停止", + "Current Time": "現在の時間", + "Duration Time": "長さ", + "Remaining Time": "残りの時間", + "Stream Type": "ストリームの種類", + "LIVE": "ライブ", + "Loaded": "ロード済み", + "Progress": "進行状況", + "Fullscreen": "フルスクリーン", + "Non-Fullscreen": "フルスクリーン以外", + "Mute": "ミュート", + "Unmute": "ミュート解除", + "Playback Rate": "再生レート", + "Subtitles": "サブタイトル", + "subtitles off": "サブタイトル オフ", + "Captions": "キャプション", + "captions off": "キャプション オフ", + "Chapters": "チャプター", + "You aborted the media playback": "動画再生を中止しました", + "A network error caused the media download to fail part-way.": "ネットワーク エラーにより動画のダウンロードが途中で失敗しました", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "サーバーまたはネットワークのエラー、またはフォーマットがサポートされていないため、動画をロードできませんでした", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "破損の問題、またはお使いのブラウザがサポートしていない機能が動画に使用されていたため、動画の再生が中止されました", + "No compatible source was found for this media.": "この動画に対して互換性のあるソースが見つかりませんでした" +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/ko.js b/vendor/assets/javascripts/lang/ko.js new file mode 100644 index 0000000..ee3ce2a --- /dev/null +++ b/vendor/assets/javascripts/lang/ko.js @@ -0,0 +1,26 @@ +videojs.addLanguage("ko",{ + "Play": "재생", + "Pause": "일시중지", + "Current Time": "현재 시간", + "Duration Time": "지정 기간", + "Remaining Time": "남은 시간", + "Stream Type": "스트리밍 유형", + "LIVE": "라이브", + "Loaded": "로드됨", + "Progress": "진행", + "Fullscreen": "전체 화면", + "Non-Fullscreen": "전체 화면 해제", + "Mute": "음소거", + "Unmute": "음소거 해제", + "Playback Rate": "재생 비율", + "Subtitles": "서브타이틀", + "subtitles off": "서브타이틀 끄기", + "Captions": "자막", + "captions off": "자막 끄기", + "Chapters": "챕터", + "You aborted the media playback": "비디오 재생을 취소했습니다.", + "A network error caused the media download to fail part-way.": "네트워크 오류로 인하여 비디오 일부를 다운로드하지 못 했습니다.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "비디오를 로드할 수 없습니다. 서버 혹은 네트워크 오류 때문이거나 지원되지 않는 형식 때문일 수 있습니다.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "비디오 재생이 취소됐습니다. 비디오가 손상되었거나 비디오가 사용하는 기능을 브라우저에서 지원하지 않는 것 같습니다.", + "No compatible source was found for this media.": "비디오에 호환되지 않는 소스가 있습니다." +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/nb.js b/vendor/assets/javascripts/lang/nb.js new file mode 100644 index 0000000..e4bbcc9 --- /dev/null +++ b/vendor/assets/javascripts/lang/nb.js @@ -0,0 +1,26 @@ +videojs.addLanguage("nb",{ + "Play": "Spill", + "Pause": "Pause", + "Current Time": "Aktuell tid", + "Duration Time": "Varighet", + "Remaining Time": "Gjenstående tid", + "Stream Type": "Type strøm", + "LIVE": "DIREKTE", + "Loaded": "Lastet inn", + "Progress": "Status", + "Fullscreen": "Fullskjerm", + "Non-Fullscreen": "Lukk fullskjerm", + "Mute": "Lyd av", + "Unmute": "Lyd på", + "Playback Rate": "Avspillingsrate", + "Subtitles": "Undertekst på", + "subtitles off": "Undertekst av", + "Captions": "Undertekst for hørselshemmede på", + "captions off": "Undertekst for hørselshemmede av", + "Chapters": "Kapitler", + "You aborted the media playback": "Du avbrøt avspillingen.", + "A network error caused the media download to fail part-way.": "En nettverksfeil avbrøt nedlasting av videoen.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "Videoen kunne ikke lastes ned, på grunn av nettverksfeil eller serverfeil, eller fordi formatet ikke er støttet.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Videoavspillingen ble avbrudt på grunn av ødelagte data eller fordi videoen ville gjøre noe som nettleseren din ikke har støtte for.", + "No compatible source was found for this media.": "Fant ikke en kompatibel kilde for dette mediainnholdet." +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/nl.js b/vendor/assets/javascripts/lang/nl.js new file mode 100644 index 0000000..6834766 --- /dev/null +++ b/vendor/assets/javascripts/lang/nl.js @@ -0,0 +1,37 @@ +videojs.addLanguage("nl",{ + "Play": "Afspelen", + "Pause": "Pauze", + "Current Time": "Huidige tijd", + "Duration Time": "Looptijd", + "Remaining Time": "Resterende tijd", + "Stream Type": "Streamtype", + "LIVE": "LIVE", + "Loaded": "Geladen", + "Progress": "Status", + "Fullscreen": "Volledig scherm", + "Non-Fullscreen": "Geen volledig scherm", + "Mute": "Geluid uit", + "Unmute": "Geluid aan", + "Playback Rate": "Weergavesnelheid", + "Subtitles": "Ondertiteling", + "subtitles off": "ondertiteling uit", + "Captions": "Bijschriften", + "captions off": "bijschriften uit", + "Chapters": "Hoofdstukken", + "Descriptions": "Beschrijvingen", + "descriptions off": "beschrijvingen off", + "You aborted the media playback": "U hebt de mediaweergave afgebroken.", + "A network error caused the media download to fail part-way.": "De mediadownload is mislukt door een netwerkfout.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "De media kon niet worden geladen, vanwege een server- of netwerkfout of doordat het formaat niet wordt ondersteund.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "De mediaweergave is afgebroken vanwege beschadigde data of het mediabestand gebruikt functies die niet door uw browser worden ondersteund.", + "No compatible source was found for this media.": "Voor deze media is geen ondersteunde bron gevonden.", + "Play Video": "Video Afspelen", + "Close": "Sluiten", + "Modal Window": "Modal Venster", + "This is a modal window": "Dit is een modaal venster", + "This modal can be closed by pressing the Escape key or activating the close button.": "Dit modaal venster kan gesloten worden door op Escape te drukken of de 'sluiten' knop te activeren.", + ", opens captions settings dialog": ", opent bijschriften instellingen venster", + ", opens subtitles settings dialog": ", opent ondertiteling instellingen venster", + ", opens descriptions settings dialog": ", opent beschrijvingen instellingen venster", + ", selected": ", selected" +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/nn.js b/vendor/assets/javascripts/lang/nn.js new file mode 100644 index 0000000..19f625e --- /dev/null +++ b/vendor/assets/javascripts/lang/nn.js @@ -0,0 +1,26 @@ +videojs.addLanguage("nn",{ + "Play": "Spel", + "Pause": "Pause", + "Current Time": "Aktuell tid", + "Duration Time": "Varigheit", + "Remaining Time": "Tid attende", + "Stream Type": "Type straum", + "LIVE": "DIREKTE", + "Loaded": "Lasta inn", + "Progress": "Status", + "Fullscreen": "Fullskjerm", + "Non-Fullscreen": "Stenga fullskjerm", + "Mute": "Ljod av", + "Unmute": "Ljod på", + "Playback Rate": "Avspelingsrate", + "Subtitles": "Teksting på", + "subtitles off": "Teksting av", + "Captions": "Teksting for høyrselshemma på", + "captions off": "Teksting for høyrselshemma av", + "Chapters": "Kapitel", + "You aborted the media playback": "Du avbraut avspelinga.", + "A network error caused the media download to fail part-way.": "Ein nettverksfeil avbraut nedlasting av videoen.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "Videoen kunne ikkje lastas ned, på grunn av ein nettverksfeil eller serverfeil, eller av di formatet ikkje er stoda.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Videoavspelinga blei broten på grunn av øydelagde data eller av di videoen ville gjera noe som nettlesaren din ikkje stodar.", + "No compatible source was found for this media.": "Fant ikke en kompatibel kilde for dette mediainnholdet." +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/pl.js b/vendor/assets/javascripts/lang/pl.js new file mode 100644 index 0000000..1946728 --- /dev/null +++ b/vendor/assets/javascripts/lang/pl.js @@ -0,0 +1,34 @@ +videojs.addLanguage("pl",{ + "Play": "Odtwarzaj", + "Pause": "Pauza", + "Current Time": "Aktualny czas", + "Duration Time": "Czas trwania", + "Remaining Time": "Pozostały czas", + "Stream Type": "Typ strumienia", + "LIVE": "NA ŻYWO", + "Loaded": "Załadowany", + "Progress": "Status", + "Fullscreen": "Pełny ekran", + "Non-Fullscreen": "Pełny ekran niedostępny", + "Mute": "Wyłącz dźwięk", + "Unmute": "Włącz dźwięk", + "Playback Rate": "Szybkość odtwarzania", + "Subtitles": "Napisy", + "subtitles off": "Napisy wyłączone", + "Captions": "Transkrypcja", + "captions off": "Transkrypcja wyłączona", + "Chapters": "Rozdziały", + "You aborted the media playback": "Odtwarzanie zostało przerwane", + "A network error caused the media download to fail part-way.": "Problemy z siecią spowodowały błąd przy pobieraniu materiału wideo.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "Materiał wideo nie może być załadowany, ponieważ wystąpił problem z siecią lub format nie jest obsługiwany", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Odtwarzanie materiału wideo zostało przerwane z powodu uszkodzonego pliku wideo lub z powodu błędu funkcji, które nie są wspierane przez przeglądarkę.", + "No compatible source was found for this media.": "Dla tego materiału wideo nie znaleziono kompatybilnego źródła.", + "Play video": "Odtwarzaj wideo", + "Close": "Zamknij", + "Modal Window": "Okno Modala", + "This is a modal window": "To jest okno modala", + "This modal can be closed by pressing the Escape key or activating the close button.": "Ten modal możesz zamknąć naciskając przycisk Escape albo wybierając przycisk Zamknij.", + ", opens captions settings dialog": ", otwiera okno dialogowe ustawień transkrypcji", + ", opens subtitles settings dialog": ", otwiera okno dialogowe napisów", + ", selected": ", zaznaczone" +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/pt-BR.js b/vendor/assets/javascripts/lang/pt-BR.js new file mode 100644 index 0000000..5e94fa1 --- /dev/null +++ b/vendor/assets/javascripts/lang/pt-BR.js @@ -0,0 +1,26 @@ +videojs.addLanguage("pt-BR",{ + "Play": "Tocar", + "Pause": "Pausar", + "Current Time": "Tempo", + "Duration Time": "Duração", + "Remaining Time": "Tempo Restante", + "Stream Type": "Tipo de Stream", + "LIVE": "AO VIVO", + "Loaded": "Carregado", + "Progress": "Progresso", + "Fullscreen": "Tela Cheia", + "Non-Fullscreen": "Tela Normal", + "Mute": "Mudo", + "Unmute": "Habilitar Som", + "Playback Rate": "Velocidade", + "Subtitles": "Legendas", + "subtitles off": "Sem Legendas", + "Captions": "Anotações", + "captions off": "Sem Anotações", + "Chapters": "Capítulos", + "You aborted the media playback": "Você parou a execução do vídeo.", + "A network error caused the media download to fail part-way.": "Um erro na rede fez o vídeo parar parcialmente.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "O vídeo não pode ser carregado, ou porque houve um problema com sua rede ou pelo formato do vídeo não ser suportado.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "A execução foi interrompida por um problema com o vídeo ou por seu navegador não dar suporte ao seu formato.", + "No compatible source was found for this media.": "Não foi encontrada fonte de vídeo compatível." +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/ru.js b/vendor/assets/javascripts/lang/ru.js new file mode 100644 index 0000000..4125eea --- /dev/null +++ b/vendor/assets/javascripts/lang/ru.js @@ -0,0 +1,26 @@ +videojs.addLanguage("ru",{ + "Play": "Воспроизвести", + "Pause": "Приостановить", + "Current Time": "Текущее время", + "Duration Time": "Продолжительность", + "Remaining Time": "Оставшееся время", + "Stream Type": "Тип потока", + "LIVE": "ОНЛАЙН", + "Loaded": "Загрузка", + "Progress": "Прогресс", + "Fullscreen": "Полноэкранный режим", + "Non-Fullscreen": "Неполноэкранный режим", + "Mute": "Без звука", + "Unmute": "Со звуком", + "Playback Rate": "Скорость воспроизведения", + "Subtitles": "Субтитры", + "subtitles off": "Субтитры выкл.", + "Captions": "Подписи", + "captions off": "Подписи выкл.", + "Chapters": "Главы", + "You aborted the media playback": "Вы прервали воспроизведение видео", + "A network error caused the media download to fail part-way.": "Ошибка сети вызвала сбой во время загрузки видео.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "Невозможно загрузить видео из-за сетевого или серверного сбоя либо формат не поддерживается.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Воспроизведение видео было приостановлено из-за повреждения либо в связи с тем, что видео использует функции, неподдерживаемые вашим браузером.", + "No compatible source was found for this media.": "Совместимые источники для этого видео отсутствуют." +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/sr.js b/vendor/assets/javascripts/lang/sr.js new file mode 100644 index 0000000..7c042b2 --- /dev/null +++ b/vendor/assets/javascripts/lang/sr.js @@ -0,0 +1,26 @@ +videojs.addLanguage("sr",{ + "Play": "Pusti", + "Pause": "Pauza", + "Current Time": "Trenutno vrijeme", + "Duration Time": "Vrijeme trajanja", + "Remaining Time": "Preostalo vrijeme", + "Stream Type": "Način strimovanja", + "LIVE": "UŽIVO", + "Loaded": "Učitan", + "Progress": "Progres", + "Fullscreen": "Puni ekran", + "Non-Fullscreen": "Mali ekran", + "Mute": "Prigušen", + "Unmute": "Ne-prigušen", + "Playback Rate": "Stopa reprodukcije", + "Subtitles": "Podnaslov", + "subtitles off": "Podnaslov deaktiviran", + "Captions": "Titlovi", + "captions off": "Titlovi deaktivirani", + "Chapters": "Poglavlja", + "You aborted the media playback": "Isključili ste reprodukciju videa.", + "A network error caused the media download to fail part-way.": "Video se prestao preuzimati zbog greške na mreži.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "Video se ne može reproducirati zbog servera, greške u mreži ili je format ne podržan.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Reprodukcija videa je zaustavljenja zbog greške u formatu ili zbog verzije vašeg pretraživača.", + "No compatible source was found for this media.": "Nije nađen nijedan kompatibilan izvor ovog videa." +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/sv.js b/vendor/assets/javascripts/lang/sv.js new file mode 100644 index 0000000..48ea77b --- /dev/null +++ b/vendor/assets/javascripts/lang/sv.js @@ -0,0 +1,26 @@ +videojs.addLanguage("sv",{ + "Play": "Spela", + "Pause": "Pausa", + "Current Time": "Aktuell tid", + "Duration Time": "Total tid", + "Remaining Time": "Återstående tid", + "Stream Type": "Strömningstyp", + "LIVE": "LIVE", + "Loaded": "Laddad", + "Progress": "Förlopp", + "Fullscreen": "Fullskärm", + "Non-Fullscreen": "Ej fullskärm", + "Mute": "Ljud av", + "Unmute": "Ljud på", + "Playback Rate": "Uppspelningshastighet", + "Subtitles": "Text på", + "subtitles off": "Text av", + "Captions": "Text på", + "captions off": "Text av", + "Chapters": "Kapitel", + "You aborted the media playback": "Du har avbrutit videouppspelningen.", + "A network error caused the media download to fail part-way.": "Ett nätverksfel gjorde att nedladdningen av videon avbröts.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "Det gick inte att ladda videon, antingen på grund av ett server- eller nätverksfel, eller för att formatet inte stöds.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Uppspelningen avbröts på grund av att videon är skadad, eller också för att videon använder funktioner som din webbläsare inte stöder.", + "No compatible source was found for this media.": "Det gick inte att hitta någon kompatibel källa för den här videon." +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/tr.js b/vendor/assets/javascripts/lang/tr.js new file mode 100644 index 0000000..8a160dc --- /dev/null +++ b/vendor/assets/javascripts/lang/tr.js @@ -0,0 +1,34 @@ +videojs.addLanguage("tr",{ + "Play": "Oynat", + "Pause": "Duraklat", + "Current Time": "Süre", + "Duration Time": "Toplam Süre", + "Remaining Time": "Kalan Süre", + "Stream Type": "Yayın Tipi", + "LIVE": "CANLI", + "Loaded": "Yüklendi", + "Progress": "Yükleniyor", + "Fullscreen": "Tam Ekran", + "Non-Fullscreen": "Küçük Ekran", + "Mute": "Ses Kapa", + "Unmute": "Ses Aç", + "Playback Rate": "Oynatma Hızı", + "Subtitles": "Altyazı", + "subtitles off": "Altyazı Kapalı", + "Captions": "Ek Açıklamalar", + "captions off": "Ek Açıklamalar Kapalı", + "Chapters": "Bölümler", + "You aborted the media playback": "Video oynatmayı iptal ettiniz", + "A network error caused the media download to fail part-way.": "Video indirilirken bağlantı sorunu oluştu.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "Video oynatılamadı, ağ ya da sunucu hatası veya belirtilen format desteklenmiyor.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Tarayıcınız desteklemediği için videoda hata oluştu.", + "No compatible source was found for this media.": "Video için kaynak bulunamadı.", + "Play Video": "Videoyu Oynat", + "Close": "Kapat", + "Modal Window": "Modal Penceresi", + "This is a modal window": "Bu bir modal penceresidir", + "This modal can be closed by pressing the Escape key or activating the close button.": "Bu modal ESC tuşuna basarak ya da kapata tıklanarak kapatılabilir.", + ", opens captions settings dialog": ", ek açıklama ayarları menüsünü açar", + ", opens subtitles settings dialog": ", altyazı ayarları menüsünü açar", + ", selected": ", seçildi" +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/uk.js b/vendor/assets/javascripts/lang/uk.js new file mode 100644 index 0000000..626924a --- /dev/null +++ b/vendor/assets/javascripts/lang/uk.js @@ -0,0 +1,26 @@ +videojs.addLanguage("uk",{ + "Play": "Відтворити", + "Pause": "Призупинити", + "Current Time": "Поточний час", + "Duration Time": "Тривалість", + "Remaining Time": "Час, що залишився", + "Stream Type": "Тип потоку", + "LIVE": "НАЖИВО", + "Loaded": "Завантаження", + "Progress": "Прогрес", + "Fullscreen": "Повноекранний режим", + "Non-Fullscreen": "Неповноекранний режим", + "Mute": "Без звуку", + "Unmute": "Зі звуком", + "Playback Rate": "Швидкість відтворення", + "Subtitles": "Субтитри", + "subtitles off": "Без субтитрів", + "Captions": "Підписи", + "captions off": "Без підписів", + "Chapters": "Розділи", + "You aborted the media playback": "Ви припинили відтворення відео", + "A network error caused the media download to fail part-way.": "Помилка мережі викликала збій під час завантаження відео.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "Неможливо завантажити відео через мережевий чи серверний збій або формат не підтримується.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Відтворення відео було припинено через пошкодження або у зв'язку з тим, що відео використовує функції, які не підтримуються вашим браузером.", + "No compatible source was found for this media.": "Сумісні джерела для цього відео відсутні." +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/vi.js b/vendor/assets/javascripts/lang/vi.js new file mode 100644 index 0000000..246710c --- /dev/null +++ b/vendor/assets/javascripts/lang/vi.js @@ -0,0 +1,26 @@ +videojs.addLanguage("vi",{ + "Play": "Phát", + "Pause": "Tạm dừng", + "Current Time": "Thời gian hiện tại", + "Duration Time": "Độ dài", + "Remaining Time": "Thời gian còn lại", + "Stream Type": "Kiểu Stream", + "LIVE": "TRỰC TIẾP", + "Loaded": "Đã tải", + "Progress": "Tiến trình", + "Fullscreen": "Toàn màn hình", + "Non-Fullscreen": "Thoát toàn màn hình", + "Mute": "Tắt tiếng", + "Unmute": "Bật âm thanh", + "Playback Rate": "Tốc độ phát", + "Subtitles": "Phụ đề", + "subtitles off": "Tắt phụ đề", + "Captions": "Chú thích", + "captions off": "Tắt chú thích", + "Chapters": "Chương", + "You aborted the media playback": "Bạn đã hủy việc phát media.", + "A network error caused the media download to fail part-way.": "Một lỗi mạng dẫn đến việc tải media bị lỗi.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "Video không tải được, mạng hay server có lỗi hoặc định dạng không được hỗ trợ.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Phát media đã bị hủy do một sai lỗi hoặc media sử dụng những tính năng trình duyệt không hỗ trợ.", + "No compatible source was found for this media.": "Không có nguồn tương thích cho media này." +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/zh-CN.js b/vendor/assets/javascripts/lang/zh-CN.js new file mode 100644 index 0000000..043c2a2 --- /dev/null +++ b/vendor/assets/javascripts/lang/zh-CN.js @@ -0,0 +1,27 @@ +videojs.addLanguage("zh-CN",{ + "Play": "播放", + "Pause": "暂停", + "Current Time": "当前时间", + "Duration Time": "时长", + "Remaining Time": "剩余时间", + "Stream Type": "媒体流类型", + "LIVE": "直播", + "Loaded": "加载完毕", + "Progress": "进度", + "Fullscreen": "全屏", + "Non-Fullscreen": "退出全屏", + "Mute": "静音", + "Unmute": "取消静音", + "Playback Rate": "播放码率", + "Subtitles": "字幕", + "subtitles off": "字幕关闭", + "Captions": "内嵌字幕", + "captions off": "内嵌字幕关闭", + "Chapters": "节目段落", + "You aborted the media playback": "视频播放被终止", + "A network error caused the media download to fail part-way.": "网络错误导致视频下载中途失败。", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "视频因格式不支持或者服务器或网络的问题无法加载。", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "由于视频文件损坏或是该视频使用了你的浏览器不支持的功能,播放终止。", + "No compatible source was found for this media.": "无法找到此视频兼容的源。", + "The media is encrypted and we do not have the keys to decrypt it.": "视频已加密,无法解密。" +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/lang/zh-TW.js b/vendor/assets/javascripts/lang/zh-TW.js new file mode 100644 index 0000000..bfb3e23 --- /dev/null +++ b/vendor/assets/javascripts/lang/zh-TW.js @@ -0,0 +1,27 @@ +videojs.addLanguage("zh-TW",{ + "Play": "播放", + "Pause": "暫停", + "Current Time": "目前時間", + "Duration Time": "總共時間", + "Remaining Time": "剩餘時間", + "Stream Type": "串流類型", + "LIVE": "直播", + "Loaded": "載入完畢", + "Progress": "進度", + "Fullscreen": "全螢幕", + "Non-Fullscreen": "退出全螢幕", + "Mute": "靜音", + "Unmute": "取消靜音", + "Playback Rate": " 播放速率", + "Subtitles": "字幕", + "subtitles off": "關閉字幕", + "Captions": "內嵌字幕", + "captions off": "關閉內嵌字幕", + "Chapters": "章節", + "You aborted the media playback": "影片播放已終止", + "A network error caused the media download to fail part-way.": "網路錯誤導致影片下載失敗。", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "影片因格式不支援或者伺服器或網路的問題無法載入。", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "由於影片檔案損毀或是該影片使用了您的瀏覽器不支援的功能,播放終止。", + "No compatible source was found for this media.": "無法找到相容此影片的來源。", + "The media is encrypted and we do not have the keys to decrypt it.": "影片已加密,無法解密。" +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/video-js.swf b/vendor/assets/javascripts/video-js.swf index 7e0822ec7868a870c451031eb3fec3ee54526f10..a36648f3e345899a1c8d314465deb3f5eea1fef0 100644 GIT binary patch literal 16794 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 + * Copyright Brightcove, Inc. + * Available under Apache License Version 2.0 + * + * + * Includes vtt.js + * Available under Apache License Version 2.0 + * */ -// HTML5 Shiv. Must be in to support older browsers. -document.createElement('video'); -document.createElement('audio'); -document.createElement('track'); +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.videojs = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 2 ? arguments[2] : {}; + var props = keys(map); + if (hasSymbols) { + props = props.concat(Object.getOwnPropertySymbols(map)); + } + foreach(props, function (name) { + defineProperty(object, name, map[name], predicates[name]); + }); +}; + +defineProperties.supportsDescriptors = !!supportsDescriptors; + +module.exports = defineProperties; + +},{"foreach":4,"object-keys":49}],3:[function(_dereq_,module,exports){ +var isFunction = _dereq_('is-function') + +module.exports = forEach + +var toString = Object.prototype.toString +var hasOwnProperty = Object.prototype.hasOwnProperty + +function forEach(list, iterator, context) { + if (!isFunction(iterator)) { + throw new TypeError('iterator must be a function') } - // If a player instance has already been created for this ID return it. - if (vjs.players[id]) { + if (arguments.length < 3) { + context = this + } - // If options or ready funtion are passed, warn - if (options) { - vjs.log.warn ('Player "' + id + '" is already initialised. Options will not be applied.'); - } + if (toString.call(list) === '[object Array]') + forEachArray(list, iterator, context) + else if (typeof list === 'string') + forEachString(list, iterator, context) + else + forEachObject(list, iterator, context) +} - if (ready) { - vjs.players[id].ready(ready); - } +function forEachArray(array, iterator, context) { + for (var i = 0, len = array.length; i < len; i++) { + if (hasOwnProperty.call(array, i)) { + iterator.call(context, array[i], i, array) + } + } +} - return vjs.players[id]; +function forEachString(string, iterator, context) { + for (var i = 0, len = string.length; i < len; i++) { + // no such thing as a sparse string. + iterator.call(context, string.charAt(i), i, string) + } +} - // Otherwise get element for ID - } else { - tag = vjs.el(id); +function forEachObject(object, iterator, context) { + for (var k in object) { + if (hasOwnProperty.call(object, k)) { + iterator.call(context, object[k], k, object) + } } +} - // ID is a media element - } else { - tag = id; - } +},{"is-function":9}],4:[function(_dereq_,module,exports){ - // Check for a useable element - if (!tag || !tag.nodeName) { // re: nodeName, could be a box div also - throw new TypeError('The element or ID supplied is not valid. (videojs)'); // Returns - } +var hasOwn = Object.prototype.hasOwnProperty; +var toString = Object.prototype.toString; - // Element may have a player attr referring to an already created player instance. - // If not, set up a new player and return the instance. - return tag['player'] || new vjs.Player(tag, options, ready); +module.exports = function forEach (obj, fn, ctx) { + if (toString.call(fn) !== '[object Function]') { + throw new TypeError('iterator must be a function'); + } + var l = obj.length; + if (l === +l) { + for (var i = 0; i < l; i++) { + fn.call(ctx, obj[i], i, obj); + } + } else { + for (var k in obj) { + if (hasOwn.call(obj, k)) { + fn.call(ctx, obj[k], k, obj); + } + } + } }; -// Extended name, also available externally, window.videojs -var videojs = window['videojs'] = vjs; -// CDN Version. Used to target right flash swf. -vjs.CDN_VERSION = '4.12'; -vjs.ACCESS_PROTOCOL = ('https:' == document.location.protocol ? 'https://' : 'http://'); +},{}],5:[function(_dereq_,module,exports){ +var ERROR_MESSAGE = 'Function.prototype.bind called on incompatible '; +var slice = Array.prototype.slice; +var toStr = Object.prototype.toString; +var funcType = '[object Function]'; -/** -* Full player version -* @type {string} -*/ -vjs['VERSION'] = '4.12.15'; +module.exports = function bind(that) { + var target = this; + if (typeof target !== 'function' || toStr.call(target) !== funcType) { + throw new TypeError(ERROR_MESSAGE + target); + } + var args = slice.call(arguments, 1); + + var bound; + var binder = function () { + if (this instanceof bound) { + var result = target.apply( + this, + args.concat(slice.call(arguments)) + ); + if (Object(result) === result) { + return result; + } + return this; + } else { + return target.apply( + that, + args.concat(slice.call(arguments)) + ); + } + }; -/** - * Global Player instance options, surfaced from vjs.Player.prototype.options_ - * vjs.options = vjs.Player.prototype.options_ - * All options should use string keys so they avoid - * renaming by closure compiler - * @type {Object} - */ -vjs.options = { - // Default order of fallback technology - 'techOrder': ['html5','flash'], - // techOrder: ['flash','html5'], + var boundLength = Math.max(0, target.length - args.length); + var boundArgs = []; + for (var i = 0; i < boundLength; i++) { + boundArgs.push('$' + i); + } - 'html5': {}, - 'flash': {}, + bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this,arguments); }')(binder); - // Default of web browser is 300x150. Should rely on source width/height. - 'width': 300, - 'height': 150, - // defaultVolume: 0.85, - 'defaultVolume': 0.00, // The freakin seaguls are driving me crazy! + if (target.prototype) { + var Empty = function Empty() {}; + Empty.prototype = target.prototype; + bound.prototype = new Empty(); + Empty.prototype = null; + } - // default playback rates - 'playbackRates': [], - // Add playback rate selection by adding rates - // 'playbackRates': [0.5, 1, 1.5, 2], + return bound; +}; - // default inactivity timeout - 'inactivityTimeout': 2000, +},{}],6:[function(_dereq_,module,exports){ +var implementation = _dereq_('./implementation'); - // Included control sets - 'children': { - 'mediaLoader': {}, - 'posterImage': {}, - 'loadingSpinner': {}, - 'textTrackDisplay': {}, - 'bigPlayButton': {}, - 'controlBar': {}, - 'errorDisplay': {}, - 'textTrackSettings': {} - }, - - 'language': document.getElementsByTagName('html')[0].getAttribute('lang') || navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language || 'en', +module.exports = Function.prototype.bind || implementation; - // locales and their language translations - 'languages': {}, +},{"./implementation":5}],7:[function(_dereq_,module,exports){ +(function (global){ +var topLevel = typeof global !== 'undefined' ? global : + typeof window !== 'undefined' ? window : {} +var minDoc = _dereq_('min-document'); - // Default message to show when a video cannot be played. - 'notSupportedMessage': 'No compatible source was found for this video.' -}; +if (typeof document !== 'undefined') { + module.exports = document; +} else { + var doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4']; + + if (!doccy) { + doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc; + } -// Set CDN Version of swf -// The added (+) blocks the replace from changing this 4.12 string -if (vjs.CDN_VERSION !== 'GENERATED'+'_CDN_VSN') { - videojs.options['flash']['swf'] = "<%= asset_path('video-js.swf') %>"; + module.exports = doccy; } +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +//# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9nbG9iYWwvZG9jdW1lbnQuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyJ2YXIgdG9wTGV2ZWwgPSB0eXBlb2YgZ2xvYmFsICE9PSAndW5kZWZpbmVkJyA/IGdsb2JhbCA6XG4gICAgdHlwZW9mIHdpbmRvdyAhPT0gJ3VuZGVmaW5lZCcgPyB3aW5kb3cgOiB7fVxudmFyIG1pbkRvYyA9IHJlcXVpcmUoJ21pbi1kb2N1bWVudCcpO1xuXG5pZiAodHlwZW9mIGRvY3VtZW50ICE9PSAndW5kZWZpbmVkJykge1xuICAgIG1vZHVsZS5leHBvcnRzID0gZG9jdW1lbnQ7XG59IGVsc2Uge1xuICAgIHZhciBkb2NjeSA9IHRvcExldmVsWydfX0dMT0JBTF9ET0NVTUVOVF9DQUNIRUA0J107XG5cbiAgICBpZiAoIWRvY2N5KSB7XG4gICAgICAgIGRvY2N5ID0gdG9wTGV2ZWxbJ19fR0xPQkFMX0RPQ1VNRU5UX0NBQ0hFQDQnXSA9IG1pbkRvYztcbiAgICB9XG5cbiAgICBtb2R1bGUuZXhwb3J0cyA9IGRvY2N5O1xufVxuIl19 +},{"min-document":1}],8:[function(_dereq_,module,exports){ +(function (global){ +if (typeof window !== "undefined") { + module.exports = window; +} else if (typeof global !== "undefined") { + module.exports = global; +} else if (typeof self !== "undefined"){ + module.exports = self; +} else { + module.exports = {}; +} + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +//# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9nbG9iYWwvd2luZG93LmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiJnZW5lcmF0ZWQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlc0NvbnRlbnQiOlsiaWYgKHR5cGVvZiB3aW5kb3cgIT09IFwidW5kZWZpbmVkXCIpIHtcbiAgICBtb2R1bGUuZXhwb3J0cyA9IHdpbmRvdztcbn0gZWxzZSBpZiAodHlwZW9mIGdsb2JhbCAhPT0gXCJ1bmRlZmluZWRcIikge1xuICAgIG1vZHVsZS5leHBvcnRzID0gZ2xvYmFsO1xufSBlbHNlIGlmICh0eXBlb2Ygc2VsZiAhPT0gXCJ1bmRlZmluZWRcIil7XG4gICAgbW9kdWxlLmV4cG9ydHMgPSBzZWxmO1xufSBlbHNlIHtcbiAgICBtb2R1bGUuZXhwb3J0cyA9IHt9O1xufVxuIl19 +},{}],9:[function(_dereq_,module,exports){ +module.exports = isFunction + +var toString = Object.prototype.toString + +function isFunction (fn) { + var string = toString.call(fn) + return string === '[object Function]' || + (typeof fn === 'function' && string !== '[object RegExp]') || + (typeof window !== 'undefined' && + // IE8 and below + (fn === window.setTimeout || + fn === window.alert || + fn === window.confirm || + fn === window.prompt)) +}; + +},{}],10:[function(_dereq_,module,exports){ +var getNative = _dereq_('../internal/getNative'); + +/* Native method references for those with the same name as other `lodash` methods. */ +var nativeNow = getNative(Date, 'now'); + /** - * Utility function for adding languages to the default options. Useful for - * amending multiple language support at runtime. + * Gets the number of milliseconds that have elapsed since the Unix epoch + * (1 January 1970 00:00:00 UTC). * - * Example: vjs.addLanguage('es', {'Hello':'Hola'}); + * @static + * @memberOf _ + * @category Date + * @example * - * @param {String} code The language code or dictionary property - * @param {Object} data The data values to be translated - * @return {Object} The resulting global languages dictionary object + * _.defer(function(stamp) { + * console.log(_.now() - stamp); + * }, _.now()); + * // => logs the number of milliseconds it took for the deferred function to be invoked */ -vjs.addLanguage = function(code, data){ - if(vjs.options['languages'][code] !== undefined) { - vjs.options['languages'][code] = vjs.util.mergeOptions(vjs.options['languages'][code], data); - } else { - vjs.options['languages'][code] = data; - } - return vjs.options['languages']; +var now = nativeNow || function() { + return new Date().getTime(); }; -/** - * Global player list - * @type {Object} - */ -vjs.players = {}; +module.exports = now; -/*! - * Custom Universal Module Definition (UMD) - * - * Video.js will never be a non-browser lib so we can simplify UMD a bunch and - * still support requirejs and browserify. This also needs to be closure - * compiler compatible, so string keys are used. - */ -if (typeof define === 'function' && define['amd']) { - define('videojs', [], function(){ return videojs; }); +},{"../internal/getNative":26}],11:[function(_dereq_,module,exports){ +var isObject = _dereq_('../lang/isObject'), + now = _dereq_('../date/now'); + +/** Used as the `TypeError` message for "Functions" methods. */ +var FUNC_ERROR_TEXT = 'Expected a function'; + +/* Native method references for those with the same name as other `lodash` methods. */ +var nativeMax = Math.max; -// checking that module is an object too because of umdjs/umd#35 -} else if (typeof exports === 'object' && typeof module === 'object') { - module['exports'] = videojs; -} /** - * Core Object/Class for objects that use inheritance + constructors - * - * To create a class that can be subclassed itself, extend the CoreObject class. + * Creates a debounced function that delays invoking `func` until after `wait` + * milliseconds have elapsed since the last time the debounced function was + * invoked. The debounced function comes with a `cancel` method to cancel + * delayed invocations. Provide an options object to indicate that `func` + * should be invoked on the leading and/or trailing edge of the `wait` timeout. + * Subsequent calls to the debounced function return the result of the last + * `func` invocation. * - * var Animal = CoreObject.extend(); - * var Horse = Animal.extend(); + * **Note:** If `leading` and `trailing` options are `true`, `func` is invoked + * on the trailing edge of the timeout only if the the debounced function is + * invoked more than once during the `wait` timeout. * - * The constructor can be defined through the init property of an object argument. + * See [David Corbacho's article](http://drupalmotion.com/article/debounce-and-throttle-visual-explanation) + * for details over the differences between `_.debounce` and `_.throttle`. * - * var Animal = CoreObject.extend({ - * init: function(name, sound){ - * this.name = name; - * } - * }); + * @static + * @memberOf _ + * @category Function + * @param {Function} func The function to debounce. + * @param {number} [wait=0] The number of milliseconds to delay. + * @param {Object} [options] The options object. + * @param {boolean} [options.leading=false] Specify invoking on the leading + * edge of the timeout. + * @param {number} [options.maxWait] The maximum time `func` is allowed to be + * delayed before it's invoked. + * @param {boolean} [options.trailing=true] Specify invoking on the trailing + * edge of the timeout. + * @returns {Function} Returns the new debounced function. + * @example * - * Other methods and properties can be added the same way, or directly to the - * prototype. + * // avoid costly calculations while the window size is in flux + * jQuery(window).on('resize', _.debounce(calculateLayout, 150)); * - * var Animal = CoreObject.extend({ - * init: function(name){ - * this.name = name; - * }, - * getName: function(){ - * return this.name; - * }, - * sound: '...' - * }); + * // invoke `sendMail` when the click event is fired, debouncing subsequent calls + * jQuery('#postbox').on('click', _.debounce(sendMail, 300, { + * 'leading': true, + * 'trailing': false + * })); * - * Animal.prototype.makeSound = function(){ - * alert(this.sound); - * }; + * // ensure `batchLog` is invoked once after 1 second of debounced calls + * var source = new EventSource('/stream'); + * jQuery(source).on('message', _.debounce(batchLog, 250, { + * 'maxWait': 1000 + * })); * - * To create an instance of a class, use the create method. + * // cancel a debounced call + * var todoChanges = _.debounce(batchLog, 1000); + * Object.observe(models.todo, todoChanges); * - * var fluffy = Animal.create('Fluffy'); - * fluffy.getName(); // -> Fluffy + * Object.observe(models, function(changes) { + * if (_.find(changes, { 'user': 'todo', 'type': 'delete'})) { + * todoChanges.cancel(); + * } + * }, ['delete']); * - * Methods and properties can be overridden in subclasses. - * - * var Horse = Animal.extend({ - * sound: 'Neighhhhh!' - * }); + * // ...at some point `models.todo` is changed + * models.todo.completed = true; * - * var horsey = Horse.create('Horsey'); - * horsey.getName(); // -> Horsey - * horsey.makeSound(); // -> Alert: Neighhhhh! - * - * @class - * @constructor - */ -vjs.CoreObject = vjs['CoreObject'] = function(){}; -// Manually exporting vjs['CoreObject'] here for Closure Compiler -// because of the use of the extend/create class methods -// If we didn't do this, those functions would get flattened to something like -// `a = ...` and `this.prototype` would refer to the global object instead of -// CoreObject - -/** - * Create a new object that inherits from this Object - * - * var Animal = CoreObject.extend(); - * var Horse = Animal.extend(); - * - * @param {Object} props Functions and properties to be applied to the - * new object's prototype - * @return {vjs.CoreObject} An object that inherits from CoreObject - * @this {*} - */ -vjs.CoreObject.extend = function(props){ - var init, subObj; - - props = props || {}; - // Set up the constructor using the supplied init method - // or using the init of the parent object - // Make sure to check the unobfuscated version for external libs - init = props['init'] || props.init || this.prototype['init'] || this.prototype.init || function(){}; - // In Resig's simple class inheritance (previously used) the constructor - // is a function that calls `this.init.apply(arguments)` - // However that would prevent us from using `ParentObject.call(this);` - // in a Child constructor because the `this` in `this.init` - // would still refer to the Child and cause an infinite loop. - // We would instead have to do - // `ParentObject.prototype.init.apply(this, arguments);` - // Bleh. We're not creating a _super() function, so it's good to keep - // the parent constructor reference simple. - subObj = function(){ - init.apply(this, arguments); - }; - - // Inherit from this object's prototype - subObj.prototype = vjs.obj.create(this.prototype); - // Reset the constructor property for subObj otherwise - // instances of subObj would have the constructor of the parent Object - subObj.prototype.constructor = subObj; - - // Make the class extendable - subObj.extend = vjs.CoreObject.extend; - // Make a function for creating instances - subObj.create = vjs.CoreObject.create; + * // ...before 1 second has passed `models.todo` is deleted + * // which cancels the debounced `todoChanges` call + * delete models.todo; + */ +function debounce(func, wait, options) { + var args, + maxTimeoutId, + result, + stamp, + thisArg, + timeoutId, + trailingCall, + lastCalled = 0, + maxWait = false, + trailing = true; + + if (typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + wait = wait < 0 ? 0 : (+wait || 0); + if (options === true) { + var leading = true; + trailing = false; + } else if (isObject(options)) { + leading = !!options.leading; + maxWait = 'maxWait' in options && nativeMax(+options.maxWait || 0, wait); + trailing = 'trailing' in options ? !!options.trailing : trailing; + } + + function cancel() { + if (timeoutId) { + clearTimeout(timeoutId); + } + if (maxTimeoutId) { + clearTimeout(maxTimeoutId); + } + lastCalled = 0; + maxTimeoutId = timeoutId = trailingCall = undefined; + } - // Extend subObj's prototype with functions and other properties from props - for (var name in props) { - if (props.hasOwnProperty(name)) { - subObj.prototype[name] = props[name]; + function complete(isCalled, id) { + if (id) { + clearTimeout(id); + } + maxTimeoutId = timeoutId = trailingCall = undefined; + if (isCalled) { + lastCalled = now(); + result = func.apply(thisArg, args); + if (!timeoutId && !maxTimeoutId) { + args = thisArg = undefined; + } } } - return subObj; -}; + function delayed() { + var remaining = wait - (now() - stamp); + if (remaining <= 0 || remaining > wait) { + complete(trailingCall, maxTimeoutId); + } else { + timeoutId = setTimeout(delayed, remaining); + } + } + + function maxDelayed() { + complete(trailing, timeoutId); + } + + function debounced() { + args = arguments; + stamp = now(); + thisArg = this; + trailingCall = trailing && (timeoutId || !leading); + + if (maxWait === false) { + var leadingCall = leading && !timeoutId; + } else { + if (!maxTimeoutId && !leading) { + lastCalled = stamp; + } + var remaining = maxWait - (stamp - lastCalled), + isCalled = remaining <= 0 || remaining > maxWait; + + if (isCalled) { + if (maxTimeoutId) { + maxTimeoutId = clearTimeout(maxTimeoutId); + } + lastCalled = stamp; + result = func.apply(thisArg, args); + } + else if (!maxTimeoutId) { + maxTimeoutId = setTimeout(maxDelayed, remaining); + } + } + if (isCalled && timeoutId) { + timeoutId = clearTimeout(timeoutId); + } + else if (!timeoutId && wait !== maxWait) { + timeoutId = setTimeout(delayed, wait); + } + if (leadingCall) { + isCalled = true; + result = func.apply(thisArg, args); + } + if (isCalled && !timeoutId && !maxTimeoutId) { + args = thisArg = undefined; + } + return result; + } + debounced.cancel = cancel; + return debounced; +} + +module.exports = debounce; + +},{"../date/now":10,"../lang/isObject":39}],12:[function(_dereq_,module,exports){ +/** Used as the `TypeError` message for "Functions" methods. */ +var FUNC_ERROR_TEXT = 'Expected a function'; + +/* Native method references for those with the same name as other `lodash` methods. */ +var nativeMax = Math.max; /** - * Create a new instance of this Object class + * Creates a function that invokes `func` with the `this` binding of the + * created function and arguments from `start` and beyond provided as an array. * - * var myAnimal = Animal.create(); + * **Note:** This method is based on the [rest parameter](https://developer.mozilla.org/Web/JavaScript/Reference/Functions/rest_parameters). * - * @return {vjs.CoreObject} An instance of a CoreObject subclass - * @this {*} - */ -vjs.CoreObject.create = function(){ - // Create a new object that inherits from this object's prototype - var inst = vjs.obj.create(this.prototype); + * @static + * @memberOf _ + * @category Function + * @param {Function} func The function to apply a rest parameter to. + * @param {number} [start=func.length-1] The start position of the rest parameter. + * @returns {Function} Returns the new function. + * @example + * + * var say = _.restParam(function(what, names) { + * return what + ' ' + _.initial(names).join(', ') + + * (_.size(names) > 1 ? ', & ' : '') + _.last(names); + * }); + * + * say('hello', 'fred', 'barney', 'pebbles'); + * // => 'hello fred, barney, & pebbles' + */ +function restParam(func, start) { + if (typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + start = nativeMax(start === undefined ? (func.length - 1) : (+start || 0), 0); + return function() { + var args = arguments, + index = -1, + length = nativeMax(args.length - start, 0), + rest = Array(length); + + while (++index < length) { + rest[index] = args[start + index]; + } + switch (start) { + case 0: return func.call(this, rest); + case 1: return func.call(this, args[0], rest); + case 2: return func.call(this, args[0], args[1], rest); + } + var otherArgs = Array(start + 1); + index = -1; + while (++index < start) { + otherArgs[index] = args[index]; + } + otherArgs[start] = rest; + return func.apply(this, otherArgs); + }; +} - // Apply this constructor function to the new object - this.apply(inst, arguments); +module.exports = restParam; + +},{}],13:[function(_dereq_,module,exports){ +var debounce = _dereq_('./debounce'), + isObject = _dereq_('../lang/isObject'); + +/** Used as the `TypeError` message for "Functions" methods. */ +var FUNC_ERROR_TEXT = 'Expected a function'; - // Return the new object - return inst; -}; /** - * @fileoverview Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/) - * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible) - * This should work very similarly to jQuery's events, however it's based off the book version which isn't as - * robust as jquery's, so there's probably some differences. + * Creates a throttled function that only invokes `func` at most once per + * every `wait` milliseconds. The throttled function comes with a `cancel` + * method to cancel delayed invocations. Provide an options object to indicate + * that `func` should be invoked on the leading and/or trailing edge of the + * `wait` timeout. Subsequent calls to the throttled function return the + * result of the last `func` call. + * + * **Note:** If `leading` and `trailing` options are `true`, `func` is invoked + * on the trailing edge of the timeout only if the the throttled function is + * invoked more than once during the `wait` timeout. + * + * See [David Corbacho's article](http://drupalmotion.com/article/debounce-and-throttle-visual-explanation) + * for details over the differences between `_.throttle` and `_.debounce`. + * + * @static + * @memberOf _ + * @category Function + * @param {Function} func The function to throttle. + * @param {number} [wait=0] The number of milliseconds to throttle invocations to. + * @param {Object} [options] The options object. + * @param {boolean} [options.leading=true] Specify invoking on the leading + * edge of the timeout. + * @param {boolean} [options.trailing=true] Specify invoking on the trailing + * edge of the timeout. + * @returns {Function} Returns the new throttled function. + * @example + * + * // avoid excessively updating the position while scrolling + * jQuery(window).on('scroll', _.throttle(updatePosition, 100)); + * + * // invoke `renewToken` when the click event is fired, but not more than once every 5 minutes + * jQuery('.interactive').on('click', _.throttle(renewToken, 300000, { + * 'trailing': false + * })); + * + * // cancel a trailing throttled call + * jQuery(window).on('popstate', throttled.cancel); */ +function throttle(func, wait, options) { + var leading = true, + trailing = true; + + if (typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + if (options === false) { + leading = false; + } else if (isObject(options)) { + leading = 'leading' in options ? !!options.leading : leading; + trailing = 'trailing' in options ? !!options.trailing : trailing; + } + return debounce(func, wait, { 'leading': leading, 'maxWait': +wait, 'trailing': trailing }); +} + +module.exports = throttle; +},{"../lang/isObject":39,"./debounce":11}],14:[function(_dereq_,module,exports){ /** - * Add an event listener to element - * It stores the handler function in a separate cache object - * and adds a generic handler to the element's event, - * along with a unique id (guid) to the element. - * @param {Element|Object} elem Element or object to bind listeners to - * @param {String|Array} type Type of event to bind to. - * @param {Function} fn Event listener. + * Copies the values of `source` to `array`. + * * @private + * @param {Array} source The array to copy values from. + * @param {Array} [array=[]] The array to copy values to. + * @returns {Array} Returns `array`. */ -vjs.on = function(elem, type, fn){ - if (vjs.obj.isArray(type)) { - return _handleMultipleEvents(vjs.on, elem, type, fn); +function arrayCopy(source, array) { + var index = -1, + length = source.length; + + array || (array = Array(length)); + while (++index < length) { + array[index] = source[index]; } + return array; +} - var data = vjs.getData(elem); +module.exports = arrayCopy; - // We need a place to store all our handler data - if (!data.handlers) data.handlers = {}; +},{}],15:[function(_dereq_,module,exports){ +/** + * A specialized version of `_.forEach` for arrays without support for callback + * shorthands and `this` binding. + * + * @private + * @param {Array} array The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns `array`. + */ +function arrayEach(array, iteratee) { + var index = -1, + length = array.length; - if (!data.handlers[type]) data.handlers[type] = []; + while (++index < length) { + if (iteratee(array[index], index, array) === false) { + break; + } + } + return array; +} - if (!fn.guid) fn.guid = vjs.guid++; +module.exports = arrayEach; - data.handlers[type].push(fn); +},{}],16:[function(_dereq_,module,exports){ +/** + * Copies properties of `source` to `object`. + * + * @private + * @param {Object} source The object to copy properties from. + * @param {Array} props The property names to copy. + * @param {Object} [object={}] The object to copy properties to. + * @returns {Object} Returns `object`. + */ +function baseCopy(source, props, object) { + object || (object = {}); - if (!data.dispatcher) { - data.disabled = false; + var index = -1, + length = props.length; - data.dispatcher = function (event){ + while (++index < length) { + var key = props[index]; + object[key] = source[key]; + } + return object; +} - if (data.disabled) return; - event = vjs.fixEvent(event); +module.exports = baseCopy; - var handlers = data.handlers[event.type]; +},{}],17:[function(_dereq_,module,exports){ +var createBaseFor = _dereq_('./createBaseFor'); - if (handlers) { - // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off. - var handlersCopy = handlers.slice(0); +/** + * The base implementation of `baseForIn` and `baseForOwn` which iterates + * over `object` properties returned by `keysFunc` invoking `iteratee` for + * each property. Iteratee functions may exit iteration early by explicitly + * returning `false`. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {Function} keysFunc The function to get the keys of `object`. + * @returns {Object} Returns `object`. + */ +var baseFor = createBaseFor(); - for (var m = 0, n = handlersCopy.length; m < n; m++) { - if (event.isImmediatePropagationStopped()) { - break; - } else { - handlersCopy[m].call(elem, event); - } - } - } - }; - } +module.exports = baseFor; - if (data.handlers[type].length == 1) { - if (elem.addEventListener) { - elem.addEventListener(type, data.dispatcher, false); - } else if (elem.attachEvent) { - elem.attachEvent('on' + type, data.dispatcher); - } - } -}; +},{"./createBaseFor":24}],18:[function(_dereq_,module,exports){ +var baseFor = _dereq_('./baseFor'), + keysIn = _dereq_('../object/keysIn'); /** - * Removes event listeners from an element - * @param {Element|Object} elem Object to remove listeners from - * @param {String|Array=} type Type of listener to remove. Don't include to remove all events from element. - * @param {Function} fn Specific listener to remove. Don't include to remove listeners for an event type. + * The base implementation of `_.forIn` without support for callback + * shorthands and `this` binding. + * * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Object} Returns `object`. */ -vjs.off = function(elem, type, fn) { - // Don't want to add a cache object through getData if not needed - if (!vjs.hasData(elem)) return; - - var data = vjs.getData(elem); +function baseForIn(object, iteratee) { + return baseFor(object, iteratee, keysIn); +} - // If no events exist, nothing to unbind - if (!data.handlers) { return; } +module.exports = baseForIn; - if (vjs.obj.isArray(type)) { - return _handleMultipleEvents(vjs.off, elem, type, fn); - } +},{"../object/keysIn":45,"./baseFor":17}],19:[function(_dereq_,module,exports){ +var arrayEach = _dereq_('./arrayEach'), + baseMergeDeep = _dereq_('./baseMergeDeep'), + isArray = _dereq_('../lang/isArray'), + isArrayLike = _dereq_('./isArrayLike'), + isObject = _dereq_('../lang/isObject'), + isObjectLike = _dereq_('./isObjectLike'), + isTypedArray = _dereq_('../lang/isTypedArray'), + keys = _dereq_('../object/keys'); - // Utility function - var removeType = function(t){ - data.handlers[t] = []; - vjs.cleanUpEvents(elem,t); - }; +/** + * The base implementation of `_.merge` without support for argument juggling, + * multiple sources, and `this` binding `customizer` functions. + * + * @private + * @param {Object} object The destination object. + * @param {Object} source The source object. + * @param {Function} [customizer] The function to customize merged values. + * @param {Array} [stackA=[]] Tracks traversed source objects. + * @param {Array} [stackB=[]] Associates values with source counterparts. + * @returns {Object} Returns `object`. + */ +function baseMerge(object, source, customizer, stackA, stackB) { + if (!isObject(object)) { + return object; + } + var isSrcArr = isArrayLike(source) && (isArray(source) || isTypedArray(source)), + props = isSrcArr ? undefined : keys(source); + + arrayEach(props || source, function(srcValue, key) { + if (props) { + key = srcValue; + srcValue = source[key]; + } + if (isObjectLike(srcValue)) { + stackA || (stackA = []); + stackB || (stackB = []); + baseMergeDeep(object, source, key, baseMerge, customizer, stackA, stackB); + } + else { + var value = object[key], + result = customizer ? customizer(value, srcValue, key, object, source) : undefined, + isCommon = result === undefined; - // Are we removing all bound events? - if (!type) { - for (var t in data.handlers) removeType(t); - return; - } + if (isCommon) { + result = srcValue; + } + if ((result !== undefined || (isSrcArr && !(key in object))) && + (isCommon || (result === result ? (result !== value) : (value === value)))) { + object[key] = result; + } + } + }); + return object; +} - var handlers = data.handlers[type]; +module.exports = baseMerge; - // If no handlers exist, nothing to unbind - if (!handlers) return; +},{"../lang/isArray":36,"../lang/isObject":39,"../lang/isTypedArray":42,"../object/keys":44,"./arrayEach":15,"./baseMergeDeep":20,"./isArrayLike":27,"./isObjectLike":32}],20:[function(_dereq_,module,exports){ +var arrayCopy = _dereq_('./arrayCopy'), + isArguments = _dereq_('../lang/isArguments'), + isArray = _dereq_('../lang/isArray'), + isArrayLike = _dereq_('./isArrayLike'), + isPlainObject = _dereq_('../lang/isPlainObject'), + isTypedArray = _dereq_('../lang/isTypedArray'), + toPlainObject = _dereq_('../lang/toPlainObject'); - // If no listener was provided, remove all listeners for type - if (!fn) { - removeType(type); - return; +/** + * A specialized version of `baseMerge` for arrays and objects which performs + * deep merges and tracks traversed objects enabling objects with circular + * references to be merged. + * + * @private + * @param {Object} object The destination object. + * @param {Object} source The source object. + * @param {string} key The key of the value to merge. + * @param {Function} mergeFunc The function to merge values. + * @param {Function} [customizer] The function to customize merged values. + * @param {Array} [stackA=[]] Tracks traversed source objects. + * @param {Array} [stackB=[]] Associates values with source counterparts. + * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + */ +function baseMergeDeep(object, source, key, mergeFunc, customizer, stackA, stackB) { + var length = stackA.length, + srcValue = source[key]; + + while (length--) { + if (stackA[length] == srcValue) { + object[key] = stackB[length]; + return; + } } + var value = object[key], + result = customizer ? customizer(value, srcValue, key, object, source) : undefined, + isCommon = result === undefined; - // We're only removing a single handler - if (fn.guid) { - for (var n = 0; n < handlers.length; n++) { - if (handlers[n].guid === fn.guid) { - handlers.splice(n--, 1); - } + if (isCommon) { + result = srcValue; + if (isArrayLike(srcValue) && (isArray(srcValue) || isTypedArray(srcValue))) { + result = isArray(value) + ? value + : (isArrayLike(value) ? arrayCopy(value) : []); + } + else if (isPlainObject(srcValue) || isArguments(srcValue)) { + result = isArguments(value) + ? toPlainObject(value) + : (isPlainObject(value) ? value : {}); + } + else { + isCommon = false; } } + // Add the source value to the stack of traversed objects and associate + // it with its merged value. + stackA.push(srcValue); + stackB.push(result); - vjs.cleanUpEvents(elem, type); -}; + if (isCommon) { + // Recursively merge objects and arrays (susceptible to call stack limits). + object[key] = mergeFunc(result, srcValue, customizer, stackA, stackB); + } else if (result === result ? (result !== value) : (value === value)) { + object[key] = result; + } +} + +module.exports = baseMergeDeep; + +},{"../lang/isArguments":35,"../lang/isArray":36,"../lang/isPlainObject":40,"../lang/isTypedArray":42,"../lang/toPlainObject":43,"./arrayCopy":14,"./isArrayLike":27}],21:[function(_dereq_,module,exports){ +var toObject = _dereq_('./toObject'); /** - * Clean up the listener cache and dispatchers - * @param {Element|Object} elem Element to clean up - * @param {String} type Type of event to clean up + * The base implementation of `_.property` without support for deep paths. + * * @private + * @param {string} key The key of the property to get. + * @returns {Function} Returns the new function. */ -vjs.cleanUpEvents = function(elem, type) { - var data = vjs.getData(elem); - - // Remove the events of a particular type if there are none left - if (data.handlers[type].length === 0) { - delete data.handlers[type]; - // data.handlers[type] = null; - // Setting to null was causing an error with data.handlers - - // Remove the meta-handler from the element - if (elem.removeEventListener) { - elem.removeEventListener(type, data.dispatcher, false); - } else if (elem.detachEvent) { - elem.detachEvent('on' + type, data.dispatcher); - } - } - - // Remove the events object if there are no types left - if (vjs.isEmpty(data.handlers)) { - delete data.handlers; - delete data.dispatcher; - delete data.disabled; +function baseProperty(key) { + return function(object) { + return object == null ? undefined : toObject(object)[key]; + }; +} - // data.handlers = null; - // data.dispatcher = null; - // data.disabled = null; - } +module.exports = baseProperty; - // Finally remove the expando if there is no data left - if (vjs.isEmpty(data)) { - vjs.removeData(elem); - } -}; +},{"./toObject":34}],22:[function(_dereq_,module,exports){ +var identity = _dereq_('../utility/identity'); /** - * Fix a native event to have standard property values - * @param {Object} event Event object to fix - * @return {Object} + * A specialized version of `baseCallback` which only supports `this` binding + * and specifying the number of arguments to provide to `func`. + * * @private + * @param {Function} func The function to bind. + * @param {*} thisArg The `this` binding of `func`. + * @param {number} [argCount] The number of arguments to provide to `func`. + * @returns {Function} Returns the callback. */ -vjs.fixEvent = function(event) { +function bindCallback(func, thisArg, argCount) { + if (typeof func != 'function') { + return identity; + } + if (thisArg === undefined) { + return func; + } + switch (argCount) { + case 1: return function(value) { + return func.call(thisArg, value); + }; + case 3: return function(value, index, collection) { + return func.call(thisArg, value, index, collection); + }; + case 4: return function(accumulator, value, index, collection) { + return func.call(thisArg, accumulator, value, index, collection); + }; + case 5: return function(value, other, key, object, source) { + return func.call(thisArg, value, other, key, object, source); + }; + } + return function() { + return func.apply(thisArg, arguments); + }; +} - function returnTrue() { return true; } - function returnFalse() { return false; } +module.exports = bindCallback; - // Test if fixing up is needed - // Used to check if !event.stopPropagation instead of isPropagationStopped - // But native events return true for stopPropagation, but don't have - // other expected methods like isPropagationStopped. Seems to be a problem - // with the Javascript Ninja code. So we're just overriding all events now. - if (!event || !event.isPropagationStopped) { - var old = event || window.event; +},{"../utility/identity":48}],23:[function(_dereq_,module,exports){ +var bindCallback = _dereq_('./bindCallback'), + isIterateeCall = _dereq_('./isIterateeCall'), + restParam = _dereq_('../function/restParam'); - event = {}; - // Clone the old object so that we can modify the values event = {}; - // IE8 Doesn't like when you mess with native event properties - // Firefox returns false for event.hasOwnProperty('type') and other props - // which makes copying more difficult. - // TODO: Probably best to create a whitelist of event props - for (var key in old) { - // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y - // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation - if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation') { - // Chrome 32+ warns if you try to copy deprecated returnValue, but - // we still want to if preventDefault isn't supported (IE8). - if (!(key == 'returnValue' && old.preventDefault)) { - event[key] = old[key]; - } - } +/** + * Creates a `_.assign`, `_.defaults`, or `_.merge` function. + * + * @private + * @param {Function} assigner The function to assign values. + * @returns {Function} Returns the new assigner function. + */ +function createAssigner(assigner) { + return restParam(function(object, sources) { + var index = -1, + length = object == null ? 0 : sources.length, + customizer = length > 2 ? sources[length - 2] : undefined, + guard = length > 2 ? sources[2] : undefined, + thisArg = length > 1 ? sources[length - 1] : undefined; + + if (typeof customizer == 'function') { + customizer = bindCallback(customizer, thisArg, 5); + length -= 2; + } else { + customizer = typeof thisArg == 'function' ? thisArg : undefined; + length -= (customizer ? 1 : 0); } - - // The event occurred on this element - if (!event.target) { - event.target = event.srcElement || document; + if (guard && isIterateeCall(sources[0], sources[1], guard)) { + customizer = length < 3 ? undefined : customizer; + length = 1; } - - // Handle which other element the event is related to - event.relatedTarget = event.fromElement === event.target ? - event.toElement : - event.fromElement; - - // Stop the default browser action - event.preventDefault = function () { - if (old.preventDefault) { - old.preventDefault(); + while (++index < length) { + var source = sources[index]; + if (source) { + assigner(object, source, customizer); } - event.returnValue = false; - event.isDefaultPrevented = returnTrue; - event.defaultPrevented = true; - }; - - event.isDefaultPrevented = returnFalse; - event.defaultPrevented = false; + } + return object; + }); +} - // Stop the event from bubbling - event.stopPropagation = function () { - if (old.stopPropagation) { - old.stopPropagation(); - } - event.cancelBubble = true; - event.isPropagationStopped = returnTrue; - }; +module.exports = createAssigner; - event.isPropagationStopped = returnFalse; +},{"../function/restParam":12,"./bindCallback":22,"./isIterateeCall":30}],24:[function(_dereq_,module,exports){ +var toObject = _dereq_('./toObject'); - // Stop the event from bubbling and executing other handlers - event.stopImmediatePropagation = function () { - if (old.stopImmediatePropagation) { - old.stopImmediatePropagation(); +/** + * Creates a base function for `_.forIn` or `_.forInRight`. + * + * @private + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new base function. + */ +function createBaseFor(fromRight) { + return function(object, iteratee, keysFunc) { + var iterable = toObject(object), + props = keysFunc(object), + length = props.length, + index = fromRight ? length : -1; + + while ((fromRight ? index-- : ++index < length)) { + var key = props[index]; + if (iteratee(iterable[key], key, iterable) === false) { + break; } - event.isImmediatePropagationStopped = returnTrue; - event.stopPropagation(); - }; - - event.isImmediatePropagationStopped = returnFalse; - - // Handle mouse position - if (event.clientX != null) { - var doc = document.documentElement, body = document.body; - - event.pageX = event.clientX + - (doc && doc.scrollLeft || body && body.scrollLeft || 0) - - (doc && doc.clientLeft || body && body.clientLeft || 0); - event.pageY = event.clientY + - (doc && doc.scrollTop || body && body.scrollTop || 0) - - (doc && doc.clientTop || body && body.clientTop || 0); } + return object; + }; +} - // Handle key presses - event.which = event.charCode || event.keyCode; - - // Fix button for mouse clicks: - // 0 == left; 1 == middle; 2 == right - if (event.button != null) { - event.button = (event.button & 1 ? 0 : - (event.button & 4 ? 1 : - (event.button & 2 ? 2 : 0))); - } - } +module.exports = createBaseFor; - // Returns fixed-up instance - return event; -}; +},{"./toObject":34}],25:[function(_dereq_,module,exports){ +var baseProperty = _dereq_('./baseProperty'); /** - * Trigger an event for an element - * @param {Element|Object} elem Element to trigger an event on - * @param {Event|Object|String} event A string (the type) or an event object with a type attribute + * Gets the "length" property value of `object`. + * + * **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792) + * that affects Safari on at least iOS 8.1-8.3 ARM64. + * * @private + * @param {Object} object The object to query. + * @returns {*} Returns the "length" value. */ -vjs.trigger = function(elem, event) { - // Fetches element data and a reference to the parent (for bubbling). - // Don't want to add a data object to cache for every parent, - // so checking hasData first. - var elemData = (vjs.hasData(elem)) ? vjs.getData(elem) : {}; - var parent = elem.parentNode || elem.ownerDocument; - // type = event.type || event, - // handler; - - // If an event name was passed as a string, creates an event out of it - if (typeof event === 'string') { - event = { type:event, target:elem }; - } - // Normalizes the event properties. - event = vjs.fixEvent(event); - - // If the passed element has a dispatcher, executes the established handlers. - if (elemData.dispatcher) { - elemData.dispatcher.call(elem, event); - } - - // Unless explicitly stopped or the event does not bubble (e.g. media events) - // recursively calls this function to bubble the event up the DOM. - if (parent && !event.isPropagationStopped() && event.bubbles !== false) { - vjs.trigger(parent, event); +var getLength = baseProperty('length'); - // If at the top of the DOM, triggers the default action unless disabled. - } else if (!parent && !event.defaultPrevented) { - var targetData = vjs.getData(event.target); - - // Checks if the target has a default action for this event. - if (event.target[event.type]) { - // Temporarily disables event dispatching on the target as we have already executed the handler. - targetData.disabled = true; - // Executes the default action. - if (typeof event.target[event.type] === 'function') { - event.target[event.type](); - } - // Re-enables event dispatching. - targetData.disabled = false; - } - } +module.exports = getLength; - // Inform the triggerer if the default was prevented by returning false - return !event.defaultPrevented; - /* Original version of js ninja events wasn't complete. - * We've since updated to the latest version, but keeping this around - * for now just in case. - */ - // // Added in addition to book. Book code was broke. - // event = typeof event === 'object' ? - // event[vjs.expando] ? - // event : - // new vjs.Event(type, event) : - // new vjs.Event(type); - - // event.type = type; - // if (handler) { - // handler.call(elem, event); - // } - - // // Clean up the event in case it is being reused - // event.result = undefined; - // event.target = elem; -}; +},{"./baseProperty":21}],26:[function(_dereq_,module,exports){ +var isNative = _dereq_('../lang/isNative'); /** - * Trigger a listener only once for an event - * @param {Element|Object} elem Element or object to - * @param {String|Array} type - * @param {Function} fn + * Gets the native function at `key` of `object`. + * * @private + * @param {Object} object The object to query. + * @param {string} key The key of the method to get. + * @returns {*} Returns the function if it's native, else `undefined`. */ -vjs.one = function(elem, type, fn) { - if (vjs.obj.isArray(type)) { - return _handleMultipleEvents(vjs.one, elem, type, fn); - } - var func = function(){ - vjs.off(elem, type, func); - fn.apply(this, arguments); - }; - // copy the guid to the new function so it can removed using the original function's ID - func.guid = fn.guid = fn.guid || vjs.guid++; - vjs.on(elem, type, func); -}; +function getNative(object, key) { + var value = object == null ? undefined : object[key]; + return isNative(value) ? value : undefined; +} + +module.exports = getNative; + +},{"../lang/isNative":38}],27:[function(_dereq_,module,exports){ +var getLength = _dereq_('./getLength'), + isLength = _dereq_('./isLength'); /** - * Loops through an array of event types and calls the requested method for each type. - * @param {Function} fn The event method we want to use. - * @param {Element|Object} elem Element or object to bind listeners to - * @param {String} type Type of event to bind to. - * @param {Function} callback Event listener. + * Checks if `value` is array-like. + * * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is array-like, else `false`. */ -function _handleMultipleEvents(fn, elem, type, callback) { - vjs.arr.forEach(type, function(type) { - fn(elem, type, callback); //Call the event method for each one of the types - }); +function isArrayLike(value) { + return value != null && isLength(getLength(value)); } -var hasOwnProp = Object.prototype.hasOwnProperty; +module.exports = isArrayLike; + +},{"./getLength":25,"./isLength":31}],28:[function(_dereq_,module,exports){ /** - * Creates an element and applies properties. - * @param {String=} tagName Name of tag to be created. - * @param {Object=} properties Element properties to be applied. - * @return {Element} + * Checks if `value` is a host object in IE < 9. + * * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a host object, else `false`. */ -vjs.createEl = function(tagName, properties){ - var el; - - tagName = tagName || 'div'; - properties = properties || {}; - - el = document.createElement(tagName); - - vjs.obj.each(properties, function(propName, val){ - // Not remembering why we were checking for dash - // but using setAttribute means you have to use getAttribute +var isHostObject = (function() { + try { + Object({ 'toString': 0 } + ''); + } catch(e) { + return function() { return false; }; + } + return function(value) { + // IE < 9 presents many host objects as `Object` objects that can coerce + // to strings despite having improperly defined `toString` methods. + return typeof value.toString != 'function' && typeof (value + '') == 'string'; + }; +}()); - // The check for dash checks for the aria-* attributes, like aria-label, aria-valuemin. - // The additional check for "role" is because the default method for adding attributes does not - // add the attribute "role". My guess is because it's not a valid attribute in some namespaces, although - // browsers handle the attribute just fine. The W3C allows for aria-* attributes to be used in pre-HTML5 docs. - // http://www.w3.org/TR/wai-aria-primer/#ariahtml. Using setAttribute gets around this problem. - if (propName.indexOf('aria-') !== -1 || propName == 'role') { - el.setAttribute(propName, val); - } else { - el[propName] = val; - } - }); +module.exports = isHostObject; - return el; -}; +},{}],29:[function(_dereq_,module,exports){ +/** Used to detect unsigned integer values. */ +var reIsUint = /^\d+$/; /** - * Uppercase the first letter of a string - * @param {String} string String to be uppercased - * @return {String} - * @private + * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer) + * of an array-like value. */ -vjs.capitalize = function(string){ - return string.charAt(0).toUpperCase() + string.slice(1); -}; +var MAX_SAFE_INTEGER = 9007199254740991; /** - * Object functions container - * @type {Object} + * Checks if `value` is a valid array-like index. + * * @private - */ -vjs.obj = {}; + * @param {*} value The value to check. + * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. + * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. + */ +function isIndex(value, length) { + value = (typeof value == 'number' || reIsUint.test(value)) ? +value : -1; + length = length == null ? MAX_SAFE_INTEGER : length; + return value > -1 && value % 1 == 0 && value < length; +} + +module.exports = isIndex; + +},{}],30:[function(_dereq_,module,exports){ +var isArrayLike = _dereq_('./isArrayLike'), + isIndex = _dereq_('./isIndex'), + isObject = _dereq_('../lang/isObject'); /** - * Object.create shim for prototypal inheritance + * Checks if the provided arguments are from an iteratee call. * - * https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create - * - * @function - * @param {Object} obj Object to use as prototype * @private + * @param {*} value The potential iteratee value argument. + * @param {*} index The potential iteratee index or key argument. + * @param {*} object The potential iteratee object argument. + * @returns {boolean} Returns `true` if the arguments are from an iteratee call, else `false`. */ -vjs.obj.create = Object.create || function(obj){ - //Create a new function called 'F' which is just an empty object. - function F() {} - - //the prototype of the 'F' function should point to the - //parameter of the anonymous function. - F.prototype = obj; +function isIterateeCall(value, index, object) { + if (!isObject(object)) { + return false; + } + var type = typeof index; + if (type == 'number' + ? (isArrayLike(object) && isIndex(index, object.length)) + : (type == 'string' && index in object)) { + var other = object[index]; + return value === value ? (value === other) : (other !== other); + } + return false; +} - //create a new constructor function based off of the 'F' function. - return new F(); -}; +module.exports = isIterateeCall; +},{"../lang/isObject":39,"./isArrayLike":27,"./isIndex":29}],31:[function(_dereq_,module,exports){ /** - * Loop through each property in an object and call a function - * whose arguments are (key,value) - * @param {Object} obj Object of properties - * @param {Function} fn Function to be called on each property. - * @this {*} - * @private + * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer) + * of an array-like value. */ -vjs.obj.each = function(obj, fn, context){ - for (var key in obj) { - if (hasOwnProp.call(obj, key)) { - fn.call(context || this, key, obj[key]); - } - } -}; +var MAX_SAFE_INTEGER = 9007199254740991; /** - * Merge two objects together and return the original. - * @param {Object} obj1 - * @param {Object} obj2 - * @return {Object} + * Checks if `value` is a valid array-like length. + * + * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength). + * * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. */ -vjs.obj.merge = function(obj1, obj2){ - if (!obj2) { return obj1; } - for (var key in obj2){ - if (hasOwnProp.call(obj2, key)) { - obj1[key] = obj2[key]; - } - } - return obj1; -}; +function isLength(value) { + return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; +} + +module.exports = isLength; +},{}],32:[function(_dereq_,module,exports){ /** - * Merge two objects, and merge any properties that are objects - * instead of just overwriting one. Uses to merge options hashes - * where deeper default settings are important. - * @param {Object} obj1 Object to override - * @param {Object} obj2 Overriding object - * @return {Object} New object. Obj1 and Obj2 will be untouched. + * Checks if `value` is object-like. + * * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. */ -vjs.obj.deepMerge = function(obj1, obj2){ - var key, val1, val2; +function isObjectLike(value) { + return !!value && typeof value == 'object'; +} - // make a copy of obj1 so we're not overwriting original values. - // like prototype.options_ and all sub options objects - obj1 = vjs.obj.copy(obj1); +module.exports = isObjectLike; - for (key in obj2){ - if (hasOwnProp.call(obj2, key)) { - val1 = obj1[key]; - val2 = obj2[key]; +},{}],33:[function(_dereq_,module,exports){ +var isArguments = _dereq_('../lang/isArguments'), + isArray = _dereq_('../lang/isArray'), + isIndex = _dereq_('./isIndex'), + isLength = _dereq_('./isLength'), + isString = _dereq_('../lang/isString'), + keysIn = _dereq_('../object/keysIn'); - // Check if both properties are pure objects and do a deep merge if so - if (vjs.obj.isPlain(val1) && vjs.obj.isPlain(val2)) { - obj1[key] = vjs.obj.deepMerge(val1, val2); - } else { - obj1[key] = obj2[key]; - } - } - } - return obj1; -}; +/** Used for native method references. */ +var objectProto = Object.prototype; -/** - * Make a copy of the supplied object - * @param {Object} obj Object to copy - * @return {Object} Copy of object - * @private - */ -vjs.obj.copy = function(obj){ - return vjs.obj.merge({}, obj); -}; +/** Used to check objects for own properties. */ +var hasOwnProperty = objectProto.hasOwnProperty; /** - * Check if an object is plain, and not a dom node or any object sub-instance - * @param {Object} obj Object to check - * @return {Boolean} True if plain, false otherwise + * A fallback implementation of `Object.keys` which creates an array of the + * own enumerable property names of `object`. + * * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. */ -vjs.obj.isPlain = function(obj){ - return !!obj - && typeof obj === 'object' - && obj.toString() === '[object Object]' - && obj.constructor === Object; -}; +function shimKeys(object) { + var props = keysIn(object), + propsLength = props.length, + length = propsLength && object.length; -/** - * Check if an object is Array -* Since instanceof Array will not work on arrays created in another frame we need to use Array.isArray, but since IE8 does not support Array.isArray we need this shim - * @param {Object} obj Object to check - * @return {Boolean} True if plain, false otherwise - * @private - */ -vjs.obj.isArray = Array.isArray || function(arr) { - return Object.prototype.toString.call(arr) === '[object Array]'; -}; + var allowIndexes = !!length && isLength(length) && + (isArray(object) || isArguments(object) || isString(object)); -/** - * Check to see whether the input is NaN or not. - * NaN is the only JavaScript construct that isn't equal to itself - * @param {Number} num Number to check - * @return {Boolean} True if NaN, false otherwise - * @private - */ -vjs.isNaN = function(num) { - return num !== num; -}; + var index = -1, + result = []; + + while (++index < propsLength) { + var key = props[index]; + if ((allowIndexes && isIndex(key, length)) || hasOwnProperty.call(object, key)) { + result.push(key); + } + } + return result; +} + +module.exports = shimKeys; + +},{"../lang/isArguments":35,"../lang/isArray":36,"../lang/isString":41,"../object/keysIn":45,"./isIndex":29,"./isLength":31}],34:[function(_dereq_,module,exports){ +var isObject = _dereq_('../lang/isObject'), + isString = _dereq_('../lang/isString'), + support = _dereq_('../support'); /** - * Bind (a.k.a proxy or Context). A simple method for changing the context of a function - It also stores a unique id on the function so it can be easily removed from events - * @param {*} context The object to bind as scope - * @param {Function} fn The function to be bound to a scope - * @param {Number=} uid An optional unique ID for the function to be set - * @return {Function} + * Converts `value` to an object if it's not one. + * * @private + * @param {*} value The value to process. + * @returns {Object} Returns the object. */ -vjs.bind = function(context, fn, uid) { - // Make sure the function has a unique ID - if (!fn.guid) { fn.guid = vjs.guid++; } +function toObject(value) { + if (support.unindexedChars && isString(value)) { + var index = -1, + length = value.length, + result = Object(value); - // Create the new function that changes the context - var ret = function() { - return fn.apply(context, arguments); - }; + while (++index < length) { + result[index] = value.charAt(index); + } + return result; + } + return isObject(value) ? value : Object(value); +} - // Allow for the ability to individualize this function - // Needed in the case where multiple objects might share the same prototype - // IF both items add an event listener with the same function, then you try to remove just one - // it will remove both because they both have the same guid. - // when using this, you need to use the bind method when you remove the listener as well. - // currently used in text tracks - ret.guid = (uid) ? uid + '_' + fn.guid : fn.guid; +module.exports = toObject; - return ret; -}; +},{"../lang/isObject":39,"../lang/isString":41,"../support":47}],35:[function(_dereq_,module,exports){ +var isArrayLike = _dereq_('../internal/isArrayLike'), + isObjectLike = _dereq_('../internal/isObjectLike'); -/** - * Element Data Store. Allows for binding data to an element without putting it directly on the element. - * Ex. Event listeners are stored here. - * (also from jsninja.com, slightly modified and updated for closure compiler) - * @type {Object} - * @private - */ -vjs.cache = {}; +/** Used for native method references. */ +var objectProto = Object.prototype; -/** - * Unique ID for an element or function - * @type {Number} - * @private - */ -vjs.guid = 1; +/** Used to check objects for own properties. */ +var hasOwnProperty = objectProto.hasOwnProperty; + +/** Native method references. */ +var propertyIsEnumerable = objectProto.propertyIsEnumerable; /** - * Unique attribute name to store an element's guid in - * @type {String} - * @constant - * @private + * Checks if `value` is classified as an `arguments` object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isArguments(function() { return arguments; }()); + * // => true + * + * _.isArguments([1, 2, 3]); + * // => false */ -vjs.expando = 'vdata' + (new Date()).getTime(); +function isArguments(value) { + return isObjectLike(value) && isArrayLike(value) && + hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee'); +} + +module.exports = isArguments; + +},{"../internal/isArrayLike":27,"../internal/isObjectLike":32}],36:[function(_dereq_,module,exports){ +var getNative = _dereq_('../internal/getNative'), + isLength = _dereq_('../internal/isLength'), + isObjectLike = _dereq_('../internal/isObjectLike'); + +/** `Object#toString` result references. */ +var arrayTag = '[object Array]'; + +/** Used for native method references. */ +var objectProto = Object.prototype; /** - * Returns the cache object where data for an element is stored - * @param {Element} el Element to store data for. - * @return {Object} - * @private + * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) + * of values. */ -vjs.getData = function(el){ - var id = el[vjs.expando]; - if (!id) { - id = el[vjs.expando] = vjs.guid++; - } - if (!vjs.cache[id]) { - vjs.cache[id] = {}; - } - return vjs.cache[id]; -}; +var objToString = objectProto.toString; + +/* Native method references for those with the same name as other `lodash` methods. */ +var nativeIsArray = getNative(Array, 'isArray'); /** - * Returns the cache object where data for an element is stored - * @param {Element} el Element to store data for. - * @return {Object} - * @private + * Checks if `value` is classified as an `Array` object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isArray([1, 2, 3]); + * // => true + * + * _.isArray(function() { return arguments; }()); + * // => false */ -vjs.hasData = function(el){ - var id = el[vjs.expando]; - return !(!id || vjs.isEmpty(vjs.cache[id])); +var isArray = nativeIsArray || function(value) { + return isObjectLike(value) && isLength(value.length) && objToString.call(value) == arrayTag; }; -/** - * Delete data for the element from the cache and the guid attr from getElementById - * @param {Element} el Remove data for an element - * @private - */ -vjs.removeData = function(el){ - var id = el[vjs.expando]; - if (!id) { return; } - // Remove all stored data - // Changed to = null - // http://coding.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/ - // vjs.cache[id] = null; - delete vjs.cache[id]; +module.exports = isArray; - // Remove the expando property from the DOM node - try { - delete el[vjs.expando]; - } catch(e) { - if (el.removeAttribute) { - el.removeAttribute(vjs.expando); - } else { - // IE doesn't appear to support removeAttribute on the document element - el[vjs.expando] = null; - } - } -}; +},{"../internal/getNative":26,"../internal/isLength":31,"../internal/isObjectLike":32}],37:[function(_dereq_,module,exports){ +var isObject = _dereq_('./isObject'); + +/** `Object#toString` result references. */ +var funcTag = '[object Function]'; + +/** Used for native method references. */ +var objectProto = Object.prototype; /** - * Check if an object is empty - * @param {Object} obj The object to check for emptiness - * @return {Boolean} - * @private + * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) + * of values. */ -vjs.isEmpty = function(obj) { - for (var prop in obj) { - // Inlude null properties as empty. - if (obj[prop] !== null) { - return false; - } - } - return true; -}; +var objToString = objectProto.toString; /** - * Check if an element has a CSS class - * @param {Element} element Element to check - * @param {String} classToCheck Classname to check - * @private - */ -vjs.hasClass = function(element, classToCheck){ - return ((' ' + element.className + ' ').indexOf(' ' + classToCheck + ' ') !== -1); -}; + * Checks if `value` is classified as a `Function` object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isFunction(_); + * // => true + * + * _.isFunction(/abc/); + * // => false + */ +function isFunction(value) { + // The use of `Object#toString` avoids issues with the `typeof` operator + // in older versions of Chrome and Safari which return 'function' for regexes + // and Safari 8 which returns 'object' for typed array constructors. + return isObject(value) && objToString.call(value) == funcTag; +} + +module.exports = isFunction; + +},{"./isObject":39}],38:[function(_dereq_,module,exports){ +var isFunction = _dereq_('./isFunction'), + isHostObject = _dereq_('../internal/isHostObject'), + isObjectLike = _dereq_('../internal/isObjectLike'); + +/** Used to detect host constructors (Safari > 5). */ +var reIsHostCtor = /^\[object .+?Constructor\]$/; + +/** Used for native method references. */ +var objectProto = Object.prototype; + +/** Used to resolve the decompiled source of functions. */ +var fnToString = Function.prototype.toString; + +/** Used to check objects for own properties. */ +var hasOwnProperty = objectProto.hasOwnProperty; +/** Used to detect if a method is native. */ +var reIsNative = RegExp('^' + + fnToString.call(hasOwnProperty).replace(/[\\^$.*+?()[\]{}|]/g, '\\$&') + .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' +); /** - * Add a CSS class name to an element - * @param {Element} element Element to add class name to - * @param {String} classToAdd Classname to add - * @private + * Checks if `value` is a native function. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a native function, else `false`. + * @example + * + * _.isNative(Array.prototype.push); + * // => true + * + * _.isNative(_); + * // => false */ -vjs.addClass = function(element, classToAdd){ - if (!vjs.hasClass(element, classToAdd)) { - element.className = element.className === '' ? classToAdd : element.className + ' ' + classToAdd; +function isNative(value) { + if (value == null) { + return false; } -}; + if (isFunction(value)) { + return reIsNative.test(fnToString.call(value)); + } + return isObjectLike(value) && (isHostObject(value) ? reIsNative : reIsHostCtor).test(value); +} + +module.exports = isNative; +},{"../internal/isHostObject":28,"../internal/isObjectLike":32,"./isFunction":37}],39:[function(_dereq_,module,exports){ /** - * Remove a CSS class name from an element - * @param {Element} element Element to remove from class name - * @param {String} classToAdd Classname to remove - * @private - */ -vjs.removeClass = function(element, classToRemove){ - var classNames, i; + * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. + * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(1); + * // => false + */ +function isObject(value) { + // Avoid a V8 JIT bug in Chrome 19-20. + // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. + var type = typeof value; + return !!value && (type == 'object' || type == 'function'); +} - if (!vjs.hasClass(element, classToRemove)) {return;} +module.exports = isObject; - classNames = element.className.split(' '); +},{}],40:[function(_dereq_,module,exports){ +var baseForIn = _dereq_('../internal/baseForIn'), + isArguments = _dereq_('./isArguments'), + isHostObject = _dereq_('../internal/isHostObject'), + isObjectLike = _dereq_('../internal/isObjectLike'), + support = _dereq_('../support'); - // no arr.indexOf in ie8, and we don't want to add a big shim - for (i = classNames.length - 1; i >= 0; i--) { - if (classNames[i] === classToRemove) { - classNames.splice(i,1); - } - } +/** `Object#toString` result references. */ +var objectTag = '[object Object]'; - element.className = classNames.join(' '); -}; +/** Used for native method references. */ +var objectProto = Object.prototype; -/** - * Element for testing browser HTML5 video capabilities - * @type {Element} - * @constant - * @private - */ -vjs.TEST_VID = vjs.createEl('video'); -(function() { - var track = document.createElement('track'); - track.kind = 'captions'; - track.srclang = 'en'; - track.label = 'English'; - vjs.TEST_VID.appendChild(track); -})(); +/** Used to check objects for own properties. */ +var hasOwnProperty = objectProto.hasOwnProperty; /** - * Useragent for browser testing. - * @type {String} - * @constant - * @private + * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) + * of values. */ -vjs.USER_AGENT = navigator.userAgent; +var objToString = objectProto.toString; /** - * Device is an iPhone - * @type {Boolean} - * @constant - * @private + * Checks if `value` is a plain object, that is, an object created by the + * `Object` constructor or one with a `[[Prototype]]` of `null`. + * + * **Note:** This method assumes objects created by the `Object` constructor + * have no inherited enumerable properties. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a plain object, else `false`. + * @example + * + * function Foo() { + * this.a = 1; + * } + * + * _.isPlainObject(new Foo); + * // => false + * + * _.isPlainObject([1, 2, 3]); + * // => false + * + * _.isPlainObject({ 'x': 0, 'y': 0 }); + * // => true + * + * _.isPlainObject(Object.create(null)); + * // => true */ -vjs.IS_IPHONE = (/iPhone/i).test(vjs.USER_AGENT); -vjs.IS_IPAD = (/iPad/i).test(vjs.USER_AGENT); -vjs.IS_IPOD = (/iPod/i).test(vjs.USER_AGENT); -vjs.IS_IOS = vjs.IS_IPHONE || vjs.IS_IPAD || vjs.IS_IPOD; - -vjs.IOS_VERSION = (function(){ - var match = vjs.USER_AGENT.match(/OS (\d+)_/i); - if (match && match[1]) { return match[1]; } -})(); - -vjs.IS_ANDROID = (/Android/i).test(vjs.USER_AGENT); -vjs.ANDROID_VERSION = (function() { - // This matches Android Major.Minor.Patch versions - // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned - var match = vjs.USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i), - major, - minor; +function isPlainObject(value) { + var Ctor; - if (!match) { - return null; + // Exit early for non `Object` objects. + if (!(isObjectLike(value) && objToString.call(value) == objectTag && !isHostObject(value) && !isArguments(value)) || + (!hasOwnProperty.call(value, 'constructor') && (Ctor = value.constructor, typeof Ctor == 'function' && !(Ctor instanceof Ctor)))) { + return false; + } + // IE < 9 iterates inherited properties before own properties. If the first + // iterated property is an object's own property then there are no inherited + // enumerable properties. + var result; + if (support.ownLast) { + baseForIn(value, function(subValue, key, object) { + result = hasOwnProperty.call(object, key); + return false; + }); + return result !== false; } + // In most environments an object's own properties are iterated before + // its inherited properties. If the last iterated property is an object's + // own property then there are no inherited enumerable properties. + baseForIn(value, function(subValue, key) { + result = key; + }); + return result === undefined || hasOwnProperty.call(value, result); +} - major = match[1] && parseFloat(match[1]); - minor = match[2] && parseFloat(match[2]); +module.exports = isPlainObject; - if (major && minor) { - return parseFloat(match[1] + '.' + match[2]); - } else if (major) { - return major; - } else { - return null; - } -})(); -// Old Android is defined as Version older than 2.3, and requiring a webkit version of the android browser -vjs.IS_OLD_ANDROID = vjs.IS_ANDROID && (/webkit/i).test(vjs.USER_AGENT) && vjs.ANDROID_VERSION < 2.3; +},{"../internal/baseForIn":18,"../internal/isHostObject":28,"../internal/isObjectLike":32,"../support":47,"./isArguments":35}],41:[function(_dereq_,module,exports){ +var isObjectLike = _dereq_('../internal/isObjectLike'); -vjs.IS_FIREFOX = (/Firefox/i).test(vjs.USER_AGENT); -vjs.IS_CHROME = (/Chrome/i).test(vjs.USER_AGENT); -vjs.IS_IE8 = (/MSIE\s8\.0/).test(vjs.USER_AGENT); +/** `Object#toString` result references. */ +var stringTag = '[object String]'; -vjs.TOUCH_ENABLED = !!(('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch); -vjs.BACKGROUND_SIZE_SUPPORTED = 'backgroundSize' in vjs.TEST_VID.style; +/** Used for native method references. */ +var objectProto = Object.prototype; /** - * Apply attributes to an HTML element. - * @param {Element} el Target element. - * @param {Object=} attributes Element attributes to be applied. - * @private + * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) + * of values. */ -vjs.setElementAttributes = function(el, attributes){ - vjs.obj.each(attributes, function(attrName, attrValue) { - if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) { - el.removeAttribute(attrName); - } else { - el.setAttribute(attrName, (attrValue === true ? '' : attrValue)); - } - }); -}; +var objToString = objectProto.toString; /** - * Get an element's attribute values, as defined on the HTML tag - * Attributes are not the same as properties. They're defined on the tag - * or with setAttribute (which shouldn't be used with HTML) - * This will return true or false for boolean attributes. - * @param {Element} tag Element from which to get tag attributes - * @return {Object} - * @private + * Checks if `value` is classified as a `String` primitive or object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isString('abc'); + * // => true + * + * _.isString(1); + * // => false */ -vjs.getElementAttributes = function(tag){ - var obj, knownBooleans, attrs, attrName, attrVal; +function isString(value) { + return typeof value == 'string' || (isObjectLike(value) && objToString.call(value) == stringTag); +} - obj = {}; +module.exports = isString; + +},{"../internal/isObjectLike":32}],42:[function(_dereq_,module,exports){ +var isLength = _dereq_('../internal/isLength'), + isObjectLike = _dereq_('../internal/isObjectLike'); + +/** `Object#toString` result references. */ +var argsTag = '[object Arguments]', + arrayTag = '[object Array]', + boolTag = '[object Boolean]', + dateTag = '[object Date]', + errorTag = '[object Error]', + funcTag = '[object Function]', + mapTag = '[object Map]', + numberTag = '[object Number]', + objectTag = '[object Object]', + regexpTag = '[object RegExp]', + setTag = '[object Set]', + stringTag = '[object String]', + weakMapTag = '[object WeakMap]'; + +var arrayBufferTag = '[object ArrayBuffer]', + float32Tag = '[object Float32Array]', + float64Tag = '[object Float64Array]', + int8Tag = '[object Int8Array]', + int16Tag = '[object Int16Array]', + int32Tag = '[object Int32Array]', + uint8Tag = '[object Uint8Array]', + uint8ClampedTag = '[object Uint8ClampedArray]', + uint16Tag = '[object Uint16Array]', + uint32Tag = '[object Uint32Array]'; + +/** Used to identify `toStringTag` values of typed arrays. */ +var typedArrayTags = {}; +typedArrayTags[float32Tag] = typedArrayTags[float64Tag] = +typedArrayTags[int8Tag] = typedArrayTags[int16Tag] = +typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] = +typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] = +typedArrayTags[uint32Tag] = true; +typedArrayTags[argsTag] = typedArrayTags[arrayTag] = +typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] = +typedArrayTags[dateTag] = typedArrayTags[errorTag] = +typedArrayTags[funcTag] = typedArrayTags[mapTag] = +typedArrayTags[numberTag] = typedArrayTags[objectTag] = +typedArrayTags[regexpTag] = typedArrayTags[setTag] = +typedArrayTags[stringTag] = typedArrayTags[weakMapTag] = false; + +/** Used for native method references. */ +var objectProto = Object.prototype; + +/** + * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) + * of values. + */ +var objToString = objectProto.toString; + +/** + * Checks if `value` is classified as a typed array. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isTypedArray(new Uint8Array); + * // => true + * + * _.isTypedArray([]); + * // => false + */ +function isTypedArray(value) { + return isObjectLike(value) && isLength(value.length) && !!typedArrayTags[objToString.call(value)]; +} - // known boolean attributes - // we can check for matching boolean properties, but older browsers - // won't know about HTML5 boolean attributes that we still read from - knownBooleans = ','+'autoplay,controls,loop,muted,default'+','; +module.exports = isTypedArray; - if (tag && tag.attributes && tag.attributes.length > 0) { - attrs = tag.attributes; +},{"../internal/isLength":31,"../internal/isObjectLike":32}],43:[function(_dereq_,module,exports){ +var baseCopy = _dereq_('../internal/baseCopy'), + keysIn = _dereq_('../object/keysIn'); - for (var i = attrs.length - 1; i >= 0; i--) { - attrName = attrs[i].name; - attrVal = attrs[i].value; +/** + * Converts `value` to a plain object flattening inherited enumerable + * properties of `value` to own properties of the plain object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to convert. + * @returns {Object} Returns the converted plain object. + * @example + * + * function Foo() { + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.assign({ 'a': 1 }, new Foo); + * // => { 'a': 1, 'b': 2 } + * + * _.assign({ 'a': 1 }, _.toPlainObject(new Foo)); + * // => { 'a': 1, 'b': 2, 'c': 3 } + */ +function toPlainObject(value) { + return baseCopy(value, keysIn(value)); +} - // check for known booleans - // the matching element property will return a value for typeof - if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(','+attrName+',') !== -1) { - // the value of an included boolean attribute is typically an empty - // string ('') which would equal false if we just check for a false value. - // we also don't want support bad code like autoplay='false' - attrVal = (attrVal !== null) ? true : false; - } +module.exports = toPlainObject; - obj[attrName] = attrVal; - } - } +},{"../internal/baseCopy":16,"../object/keysIn":45}],44:[function(_dereq_,module,exports){ +var getNative = _dereq_('../internal/getNative'), + isArrayLike = _dereq_('../internal/isArrayLike'), + isObject = _dereq_('../lang/isObject'), + shimKeys = _dereq_('../internal/shimKeys'), + support = _dereq_('../support'); - return obj; -}; +/* Native method references for those with the same name as other `lodash` methods. */ +var nativeKeys = getNative(Object, 'keys'); /** - * Get the computed style value for an element - * From http://robertnyman.com/2006/04/24/get-the-rendered-style-of-an-element/ - * @param {Element} el Element to get style value for - * @param {String} strCssRule Style name - * @return {String} Style value - * @private - */ -vjs.getComputedDimension = function(el, strCssRule){ - var strValue = ''; - if(document.defaultView && document.defaultView.getComputedStyle){ - strValue = document.defaultView.getComputedStyle(el, '').getPropertyValue(strCssRule); - - } else if(el.currentStyle){ - // IE8 Width/Height support - strValue = el['client'+strCssRule.substr(0,1).toUpperCase() + strCssRule.substr(1)] + 'px'; - } - return strValue; -}; - + * Creates an array of the own enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. See the + * [ES spec](http://ecma-international.org/ecma-262/6.0/#sec-object.keys) + * for more details. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keys(new Foo); + * // => ['a', 'b'] (iteration order is not guaranteed) + * + * _.keys('hi'); + * // => ['0', '1'] + */ +var keys = !nativeKeys ? shimKeys : function(object) { + var Ctor = object == null ? undefined : object.constructor; + if ((typeof Ctor == 'function' && Ctor.prototype === object) || + (typeof object == 'function' ? support.enumPrototypes : isArrayLike(object))) { + return shimKeys(object); + } + return isObject(object) ? nativeKeys(object) : []; +}; + +module.exports = keys; + +},{"../internal/getNative":26,"../internal/isArrayLike":27,"../internal/shimKeys":33,"../lang/isObject":39,"../support":47}],45:[function(_dereq_,module,exports){ +var arrayEach = _dereq_('../internal/arrayEach'), + isArguments = _dereq_('../lang/isArguments'), + isArray = _dereq_('../lang/isArray'), + isFunction = _dereq_('../lang/isFunction'), + isIndex = _dereq_('../internal/isIndex'), + isLength = _dereq_('../internal/isLength'), + isObject = _dereq_('../lang/isObject'), + isString = _dereq_('../lang/isString'), + support = _dereq_('../support'); + +/** `Object#toString` result references. */ +var arrayTag = '[object Array]', + boolTag = '[object Boolean]', + dateTag = '[object Date]', + errorTag = '[object Error]', + funcTag = '[object Function]', + numberTag = '[object Number]', + objectTag = '[object Object]', + regexpTag = '[object RegExp]', + stringTag = '[object String]'; + +/** Used to fix the JScript `[[DontEnum]]` bug. */ +var shadowProps = [ + 'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', + 'toLocaleString', 'toString', 'valueOf' +]; + +/** Used for native method references. */ +var errorProto = Error.prototype, + objectProto = Object.prototype, + stringProto = String.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty = objectProto.hasOwnProperty; + /** - * Insert an element as the first child node of another - * @param {Element} child Element to insert - * @param {[type]} parent Element to insert child into - * @private + * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) + * of values. */ -vjs.insertFirst = function(child, parent){ - if (parent.firstChild) { - parent.insertBefore(child, parent.firstChild); - } else { - parent.appendChild(child); +var objToString = objectProto.toString; + +/** Used to avoid iterating over non-enumerable properties in IE < 9. */ +var nonEnumProps = {}; +nonEnumProps[arrayTag] = nonEnumProps[dateTag] = nonEnumProps[numberTag] = { 'constructor': true, 'toLocaleString': true, 'toString': true, 'valueOf': true }; +nonEnumProps[boolTag] = nonEnumProps[stringTag] = { 'constructor': true, 'toString': true, 'valueOf': true }; +nonEnumProps[errorTag] = nonEnumProps[funcTag] = nonEnumProps[regexpTag] = { 'constructor': true, 'toString': true }; +nonEnumProps[objectTag] = { 'constructor': true }; + +arrayEach(shadowProps, function(key) { + for (var tag in nonEnumProps) { + if (hasOwnProperty.call(nonEnumProps, tag)) { + var props = nonEnumProps[tag]; + props[key] = hasOwnProperty.call(props, key); + } } -}; +}); /** - * Object to hold browser support information - * @type {Object} - * @private - */ -vjs.browser = {}; + * Creates an array of the own and inherited enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keysIn(new Foo); + * // => ['a', 'b', 'c'] (iteration order is not guaranteed) + */ +function keysIn(object) { + if (object == null) { + return []; + } + if (!isObject(object)) { + object = Object(object); + } + var length = object.length; + + length = (length && isLength(length) && + (isArray(object) || isArguments(object) || isString(object)) && length) || 0; + + var Ctor = object.constructor, + index = -1, + proto = (isFunction(Ctor) && Ctor.prototype) || objectProto, + isProto = proto === object, + result = Array(length), + skipIndexes = length > 0, + skipErrorProps = support.enumErrorProps && (object === errorProto || object instanceof Error), + skipProto = support.enumPrototypes && isFunction(object); + + while (++index < length) { + result[index] = (index + ''); + } + // lodash skips the `constructor` property when it infers it's iterating + // over a `prototype` object because IE < 9 can't set the `[[Enumerable]]` + // attribute of an existing property and the `constructor` property of a + // prototype defaults to non-enumerable. + for (var key in object) { + if (!(skipProto && key == 'prototype') && + !(skipErrorProps && (key == 'message' || key == 'name')) && + !(skipIndexes && isIndex(key, length)) && + !(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) { + result.push(key); + } + } + if (support.nonEnumShadows && object !== objectProto) { + var tag = object === stringProto ? stringTag : (object === errorProto ? errorTag : objToString.call(object)), + nonEnums = nonEnumProps[tag] || nonEnumProps[objectTag]; + + if (tag == objectTag) { + proto = objectProto; + } + length = shadowProps.length; + while (length--) { + key = shadowProps[length]; + var nonEnum = nonEnums[key]; + if (!(isProto && nonEnum) && + (nonEnum ? hasOwnProperty.call(object, key) : object[key] !== proto[key])) { + result.push(key); + } + } + } + return result; +} + +module.exports = keysIn; + +},{"../internal/arrayEach":15,"../internal/isIndex":29,"../internal/isLength":31,"../lang/isArguments":35,"../lang/isArray":36,"../lang/isFunction":37,"../lang/isObject":39,"../lang/isString":41,"../support":47}],46:[function(_dereq_,module,exports){ +var baseMerge = _dereq_('../internal/baseMerge'), + createAssigner = _dereq_('../internal/createAssigner'); /** - * Shorthand for document.getElementById() - * Also allows for CSS (jQuery) ID syntax. But nothing other than IDs. - * @param {String} id Element ID - * @return {Element} Element with supplied ID - * @private + * Recursively merges own enumerable properties of the source object(s), that + * don't resolve to `undefined` into the destination object. Subsequent sources + * overwrite property assignments of previous sources. If `customizer` is + * provided it's invoked to produce the merged values of the destination and + * source properties. If `customizer` returns `undefined` merging is handled + * by the method instead. The `customizer` is bound to `thisArg` and invoked + * with five arguments: (objectValue, sourceValue, key, object, source). + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @param {Function} [customizer] The function to customize assigned values. + * @param {*} [thisArg] The `this` binding of `customizer`. + * @returns {Object} Returns `object`. + * @example + * + * var users = { + * 'data': [{ 'user': 'barney' }, { 'user': 'fred' }] + * }; + * + * var ages = { + * 'data': [{ 'age': 36 }, { 'age': 40 }] + * }; + * + * _.merge(users, ages); + * // => { 'data': [{ 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 }] } + * + * // using a customizer callback + * var object = { + * 'fruits': ['apple'], + * 'vegetables': ['beet'] + * }; + * + * var other = { + * 'fruits': ['banana'], + * 'vegetables': ['carrot'] + * }; + * + * _.merge(object, other, function(a, b) { + * if (_.isArray(a)) { + * return a.concat(b); + * } + * }); + * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot'] } */ -vjs.el = function(id){ - if (id.indexOf('#') === 0) { - id = id.slice(1); - } +var merge = createAssigner(baseMerge); - return document.getElementById(id); -}; +module.exports = merge; + +},{"../internal/baseMerge":19,"../internal/createAssigner":23}],47:[function(_dereq_,module,exports){ +/** Used for native method references. */ +var arrayProto = Array.prototype, + errorProto = Error.prototype, + objectProto = Object.prototype; + +/** Native method references. */ +var propertyIsEnumerable = objectProto.propertyIsEnumerable, + splice = arrayProto.splice; /** - * Format seconds as a time string, H:MM:SS or M:SS - * Supplying a guide (in seconds) will force a number of leading zeros - * to cover the length of the guide - * @param {Number} seconds Number of seconds to be turned into a string - * @param {Number} guide Number (in seconds) to model the string after - * @return {String} Time formatted as H:MM:SS or M:SS - * @private + * An object environment feature flags. + * + * @static + * @memberOf _ + * @type Object */ -vjs.formatTime = function(seconds, guide) { - // Default to using seconds as guide - guide = guide || seconds; - var s = Math.floor(seconds % 60), - m = Math.floor(seconds / 60 % 60), - h = Math.floor(seconds / 3600), - gm = Math.floor(guide / 60 % 60), - gh = Math.floor(guide / 3600); +var support = {}; - // handle invalid times - if (isNaN(seconds) || seconds === Infinity) { - // '-' is false for all relational operators (e.g. <, >=) so this setting - // will add the minimum number of fields specified by the guide - h = m = s = '-'; - } +(function(x) { + var Ctor = function() { this.x = x; }, + object = { '0': x, 'length': x }, + props = []; - // Check if we need to show hours - h = (h > 0 || gh > 0) ? h + ':' : ''; + Ctor.prototype = { 'valueOf': x, 'y': x }; + for (var key in new Ctor) { props.push(key); } - // If hours are showing, we may need to add a leading zero. - // Always show at least one digit of minutes. - m = (((h || gm >= 10) && m < 10) ? '0' + m : m) + ':'; + /** + * Detect if `name` or `message` properties of `Error.prototype` are + * enumerable by default (IE < 9, Safari < 5.1). + * + * @memberOf _.support + * @type boolean + */ + support.enumErrorProps = propertyIsEnumerable.call(errorProto, 'message') || + propertyIsEnumerable.call(errorProto, 'name'); - // Check if leading zero is need for seconds - s = (s < 10) ? '0' + s : s; + /** + * Detect if `prototype` properties are enumerable by default. + * + * Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1 + * (if the prototype or a property on the prototype has been set) + * incorrectly set the `[[Enumerable]]` value of a function's `prototype` + * property to `true`. + * + * @memberOf _.support + * @type boolean + */ + support.enumPrototypes = propertyIsEnumerable.call(Ctor, 'prototype'); - return h + m + s; -}; + /** + * Detect if properties shadowing those on `Object.prototype` are non-enumerable. + * + * In IE < 9 an object's own properties, shadowing non-enumerable ones, + * are made non-enumerable as well (a.k.a the JScript `[[DontEnum]]` bug). + * + * @memberOf _.support + * @type boolean + */ + support.nonEnumShadows = !/valueOf/.test(props); -// Attempt to block the ability to select text while dragging controls -vjs.blockTextSelection = function(){ - document.body.focus(); - document.onselectstart = function () { return false; }; -}; -// Turn off text selection blocking -vjs.unblockTextSelection = function(){ document.onselectstart = function () { return true; }; }; + /** + * Detect if own properties are iterated after inherited properties (IE < 9). + * + * @memberOf _.support + * @type boolean + */ + support.ownLast = props[0] != 'x'; -/** - * Trim whitespace from the ends of a string. - * @param {String} string String to trim - * @return {String} Trimmed string - * @private - */ -vjs.trim = function(str){ - return (str+'').replace(/^\s+|\s+$/g, ''); -}; + /** + * Detect if `Array#shift` and `Array#splice` augment array-like objects + * correctly. + * + * Firefox < 10, compatibility modes of IE 8, and IE < 9 have buggy Array + * `shift()` and `splice()` functions that fail to remove the last element, + * `value[0]`, of array-like objects even though the "length" property is + * set to `0`. The `shift()` method is buggy in compatibility modes of IE 8, + * while `splice()` is buggy regardless of mode in IE < 9. + * + * @memberOf _.support + * @type boolean + */ + support.spliceObjects = (splice.call(object, 0, 1), !object[0]); -/** - * Should round off a number to a decimal place - * @param {Number} num Number to round - * @param {Number} dec Number of decimal places to round to - * @return {Number} Rounded number - * @private - */ -vjs.round = function(num, dec) { - if (!dec) { dec = 0; } - return Math.round(num*Math.pow(10,dec))/Math.pow(10,dec); -}; + /** + * Detect lack of support for accessing string characters by index. + * + * IE < 8 can't access characters by index. IE 8 can only access characters + * by index on string literals, not string objects. + * + * @memberOf _.support + * @type boolean + */ + support.unindexedChars = ('x'[0] + Object('x')[0]) != 'xx'; +}(1, 0)); +module.exports = support; + +},{}],48:[function(_dereq_,module,exports){ /** - * Should create a fake TimeRange object - * Mimics an HTML5 time range instance, which has functions that - * return the start and end times for a range - * TimeRanges are returned by the buffered() method - * @param {Number} start Start time in seconds - * @param {Number} end End time in seconds - * @return {Object} Fake TimeRange object - * @private + * This method returns the first argument provided to it. + * + * @static + * @memberOf _ + * @category Utility + * @param {*} value Any value. + * @returns {*} Returns `value`. + * @example + * + * var object = { 'user': 'fred' }; + * + * _.identity(object) === object; + * // => true */ -vjs.createTimeRange = function(start, end){ - if (start === undefined && end === undefined) { - return { - length: 0, - start: function() { - throw new Error('This TimeRanges object is empty'); - }, - end: function() { - throw new Error('This TimeRanges object is empty'); - } - }; - } +function identity(value) { + return value; +} - return { - length: 1, - start: function() { return start; }, - end: function() { return end; } - }; -}; +module.exports = identity; -/** - * Add to local storage (may removable) - * @private - */ -vjs.setLocalStorage = function(key, value){ - try { - // IE was throwing errors referencing the var anywhere without this - var localStorage = window.localStorage || false; - if (!localStorage) { return; } - localStorage[key] = value; - } catch(e) { - if (e.code == 22 || e.code == 1014) { // Webkit == 22 / Firefox == 1014 - vjs.log('LocalStorage Full (VideoJS)', e); - } else { - if (e.code == 18) { - vjs.log('LocalStorage not allowed (VideoJS)', e); - } else { - vjs.log('LocalStorage Error (VideoJS)', e); +},{}],49:[function(_dereq_,module,exports){ +'use strict'; + +// modified from https://github.com/es-shims/es5-shim +var has = Object.prototype.hasOwnProperty; +var toStr = Object.prototype.toString; +var slice = Array.prototype.slice; +var isArgs = _dereq_('./isArguments'); +var isEnumerable = Object.prototype.propertyIsEnumerable; +var hasDontEnumBug = !isEnumerable.call({ toString: null }, 'toString'); +var hasProtoEnumBug = isEnumerable.call(function () {}, 'prototype'); +var dontEnums = [ + 'toString', + 'toLocaleString', + 'valueOf', + 'hasOwnProperty', + 'isPrototypeOf', + 'propertyIsEnumerable', + 'constructor' +]; +var equalsConstructorPrototype = function (o) { + var ctor = o.constructor; + return ctor && ctor.prototype === o; +}; +var excludedKeys = { + $console: true, + $external: true, + $frame: true, + $frameElement: true, + $frames: true, + $innerHeight: true, + $innerWidth: true, + $outerHeight: true, + $outerWidth: true, + $pageXOffset: true, + $pageYOffset: true, + $parent: true, + $scrollLeft: true, + $scrollTop: true, + $scrollX: true, + $scrollY: true, + $self: true, + $webkitIndexedDB: true, + $webkitStorageInfo: true, + $window: true +}; +var hasAutomationEqualityBug = (function () { + /* global window */ + if (typeof window === 'undefined') { return false; } + for (var k in window) { + try { + if (!excludedKeys['$' + k] && has.call(window, k) && window[k] !== null && typeof window[k] === 'object') { + try { + equalsConstructorPrototype(window[k]); + } catch (e) { + return true; + } } + } catch (e) { + return true; } } + return false; +}()); +var equalsConstructorPrototypeIfNotBuggy = function (o) { + /* global window */ + if (typeof window === 'undefined' || !hasAutomationEqualityBug) { + return equalsConstructorPrototype(o); + } + try { + return equalsConstructorPrototype(o); + } catch (e) { + return false; + } }; -/** - * Get absolute version of relative URL. Used to tell flash correct URL. - * http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue - * @param {String} url URL to make absolute - * @return {String} Absolute URL - * @private - */ -vjs.getAbsoluteURL = function(url){ +var keysShim = function keys(object) { + var isObject = object !== null && typeof object === 'object'; + var isFunction = toStr.call(object) === '[object Function]'; + var isArguments = isArgs(object); + var isString = isObject && toStr.call(object) === '[object String]'; + var theKeys = []; - // Check if absolute URL - if (!url.match(/^https?:\/\//)) { - // Convert to absolute URL. Flash hosted off-site needs an absolute URL. - url = vjs.createEl('div', { - innerHTML: 'x' - }).firstChild.href; + if (!isObject && !isFunction && !isArguments) { + throw new TypeError('Object.keys called on a non-object'); } - return url; + var skipProto = hasProtoEnumBug && isFunction; + if (isString && object.length > 0 && !has.call(object, 0)) { + for (var i = 0; i < object.length; ++i) { + theKeys.push(String(i)); + } + } + + if (isArguments && object.length > 0) { + for (var j = 0; j < object.length; ++j) { + theKeys.push(String(j)); + } + } else { + for (var name in object) { + if (!(skipProto && name === 'prototype') && has.call(object, name)) { + theKeys.push(String(name)); + } + } + } + + if (hasDontEnumBug) { + var skipConstructor = equalsConstructorPrototypeIfNotBuggy(object); + + for (var k = 0; k < dontEnums.length; ++k) { + if (!(skipConstructor && dontEnums[k] === 'constructor') && has.call(object, dontEnums[k])) { + theKeys.push(dontEnums[k]); + } + } + } + return theKeys; }; +keysShim.shim = function shimObjectKeys() { + if (Object.keys) { + var keysWorksWithArguments = (function () { + // Safari 5.0 bug + return (Object.keys(arguments) || '').length === 2; + }(1, 2)); + if (!keysWorksWithArguments) { + var originalKeys = Object.keys; + Object.keys = function keys(object) { + if (isArgs(object)) { + return originalKeys(slice.call(object)); + } else { + return originalKeys(object); + } + }; + } + } else { + Object.keys = keysShim; + } + return Object.keys || keysShim; +}; -/** - * Resolve and parse the elements of a URL - * @param {String} url The url to parse - * @return {Object} An object of url details - */ -vjs.parseUrl = function(url) { - var div, a, addToBody, props, details; +module.exports = keysShim; - props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host']; +},{"./isArguments":50}],50:[function(_dereq_,module,exports){ +'use strict'; - // add the url to an anchor and let the browser parse the URL - a = vjs.createEl('a', { href: url }); +var toStr = Object.prototype.toString; - // IE8 (and 9?) Fix - // ie8 doesn't parse the URL correctly until the anchor is actually - // added to the body, and an innerHTML is needed to trigger the parsing - addToBody = (a.host === '' && a.protocol !== 'file:'); - if (addToBody) { - div = vjs.createEl('div'); - div.innerHTML = ''; - a = div.firstChild; - // prevent the div from affecting layout - div.setAttribute('style', 'display:none; position:absolute;'); - document.body.appendChild(div); +module.exports = function isArguments(value) { + var str = toStr.call(value); + var isArgs = str === '[object Arguments]'; + if (!isArgs) { + isArgs = str !== '[object Array]' && + value !== null && + typeof value === 'object' && + typeof value.length === 'number' && + value.length >= 0 && + toStr.call(value.callee) === '[object Function]'; } + return isArgs; +}; - // Copy the specific URL properties to a new object - // This is also needed for IE8 because the anchor loses its - // properties when it's removed from the dom - details = {}; - for (var i = 0; i < props.length; i++) { - details[props[i]] = a[props[i]]; - } +},{}],51:[function(_dereq_,module,exports){ +'use strict'; - // IE9 adds the port to the host property unlike everyone else. If - // a port identifier is added for standard ports, strip it. - if (details.protocol === 'http:') { - details.host = details.host.replace(/:80$/, ''); - } - if (details.protocol === 'https:') { - details.host = details.host.replace(/:443$/, ''); - } +var keys = _dereq_('object-keys'); - if (addToBody) { - document.body.removeChild(div); - } +module.exports = function hasSymbols() { + if (typeof Symbol !== 'function' || typeof Object.getOwnPropertySymbols !== 'function') { return false; } + if (typeof Symbol.iterator === 'symbol') { return true; } - return details; -}; + var obj = {}; + var sym = Symbol('test'); + if (typeof sym === 'string') { return false; } -/** - * Log messages to the console and history based on the type of message - * - * @param {String} type The type of message, or `null` for `log` - * @param {[type]} args The args to be passed to the log - * @private - */ -function _logType(type, args){ - var argsArray, noop, console; + // temp disabled per https://github.com/ljharb/object.assign/issues/17 + // if (sym instanceof Symbol) { return false; } + // temp disabled per https://github.com/WebReflection/get-own-property-symbols/issues/4 + // if (!(Object(sym) instanceof Symbol)) { return false; } - // convert args to an array to get array functions - argsArray = Array.prototype.slice.call(args); - // if there's no console then don't try to output messages - // they will still be stored in vjs.log.history - // Was setting these once outside of this function, but containing them - // in the function makes it easier to test cases where console doesn't exist - noop = function(){}; - console = window['console'] || { - 'log': noop, - 'warn': noop, - 'error': noop - }; + var symVal = 42; + obj[sym] = symVal; + for (sym in obj) { return false; } + if (keys(obj).length !== 0) { return false; } + if (typeof Object.keys === 'function' && Object.keys(obj).length !== 0) { return false; } - if (type) { - // add the type to the front of the message - argsArray.unshift(type.toUpperCase()+':'); - } else { - // default to log with no prefix - type = 'log'; - } + if (typeof Object.getOwnPropertyNames === 'function' && Object.getOwnPropertyNames(obj).length !== 0) { return false; } - // add to history - vjs.log.history.push(argsArray); + var syms = Object.getOwnPropertySymbols(obj); + if (syms.length !== 1 || syms[0] !== sym) { return false; } - // add console prefix after adding to history - argsArray.unshift('VIDEOJS:'); + if (!Object.prototype.propertyIsEnumerable.call(obj, sym)) { return false; } - // call appropriate log function - if (console[type].apply) { - console[type].apply(console, argsArray); - } else { - // ie8 doesn't allow error.apply, but it will just join() the array anyway - console[type](argsArray.join(' ')); + if (typeof Object.getOwnPropertyDescriptor === 'function') { + var descriptor = Object.getOwnPropertyDescriptor(obj, sym); + if (descriptor.value !== symVal || descriptor.enumerable !== true) { return false; } } -} -/** - * Log plain debug messages - */ -vjs.log = function(){ - _logType(null, arguments); + return true; }; -/** - * Keep a history of log messages - * @type {Array} - */ -vjs.log.history = []; +},{"object-keys":49}],52:[function(_dereq_,module,exports){ +'use strict'; -/** - * Log error messages - */ -vjs.log.error = function(){ - _logType('error', arguments); +// modified from https://github.com/es-shims/es6-shim +var keys = _dereq_('object-keys'); +var bind = _dereq_('function-bind'); +var canBeObject = function (obj) { + return typeof obj !== 'undefined' && obj !== null; +}; +var hasSymbols = _dereq_('./hasSymbols')(); +var toObject = Object; +var push = bind.call(Function.call, Array.prototype.push); +var propIsEnumerable = bind.call(Function.call, Object.prototype.propertyIsEnumerable); + +module.exports = function assign(target, source1) { + if (!canBeObject(target)) { throw new TypeError('target must be an object'); } + var objTarget = toObject(target); + var s, source, i, props, syms, value, key; + for (s = 1; s < arguments.length; ++s) { + source = toObject(arguments[s]); + props = keys(source); + if (hasSymbols && Object.getOwnPropertySymbols) { + syms = Object.getOwnPropertySymbols(source); + for (i = 0; i < syms.length; ++i) { + key = syms[i]; + if (propIsEnumerable(source, key)) { + push(props, key); + } + } + } + for (i = 0; i < props.length; ++i) { + key = props[i]; + value = source[key]; + if (propIsEnumerable(source, key)) { + objTarget[key] = value; + } + } + } + return objTarget; }; -/** - * Log warning messages - */ -vjs.log.warn = function(){ - _logType('warn', arguments); -}; +},{"./hasSymbols":51,"function-bind":6,"object-keys":49}],53:[function(_dereq_,module,exports){ +'use strict'; -// Offset Left -// getBoundingClientRect technique from John Resig http://ejohn.org/blog/getboundingclientrect-is-awesome/ -vjs.findPosition = function(el) { - var box, docEl, body, clientLeft, scrollLeft, left, clientTop, scrollTop, top; +var defineProperties = _dereq_('define-properties'); - if (el.getBoundingClientRect && el.parentNode) { - box = el.getBoundingClientRect(); - } +var implementation = _dereq_('./implementation'); +var getPolyfill = _dereq_('./polyfill'); +var shim = _dereq_('./shim'); - if (!box) { - return { - left: 0, - top: 0 - }; - } +defineProperties(implementation, { + implementation: implementation, + getPolyfill: getPolyfill, + shim: shim +}); - docEl = document.documentElement; - body = document.body; +module.exports = implementation; - clientLeft = docEl.clientLeft || body.clientLeft || 0; - scrollLeft = window.pageXOffset || body.scrollLeft; - left = box.left + scrollLeft - clientLeft; +},{"./implementation":52,"./polyfill":54,"./shim":55,"define-properties":2}],54:[function(_dereq_,module,exports){ +'use strict'; - clientTop = docEl.clientTop || body.clientTop || 0; - scrollTop = window.pageYOffset || body.scrollTop; - top = box.top + scrollTop - clientTop; +var implementation = _dereq_('./implementation'); - // Android sometimes returns slightly off decimal values, so need to round - return { - left: vjs.round(left), - top: vjs.round(top) - }; +var lacksProperEnumerationOrder = function () { + if (!Object.assign) { + return false; + } + // v8, specifically in node 4.x, has a bug with incorrect property enumeration order + // note: this does not detect the bug unless there's 20 characters + var str = 'abcdefghijklmnopqrst'; + var letters = str.split(''); + var map = {}; + for (var i = 0; i < letters.length; ++i) { + map[letters[i]] = letters[i]; + } + var obj = Object.assign({}, map); + var actual = ''; + for (var k in obj) { + actual += k; + } + return str !== actual; }; -/** - * Array functions container - * @type {Object} - * @private - */ -vjs.arr = {}; - -/* - * Loops through an array and runs a function for each item inside it. - * @param {Array} array The array - * @param {Function} callback The function to be run for each item - * @param {*} thisArg The `this` binding of callback - * @returns {Array} The array - * @private - */ -vjs.arr.forEach = function(array, callback, thisArg) { - if (vjs.obj.isArray(array) && callback instanceof Function) { - for (var i = 0, len = array.length; i < len; ++i) { - callback.call(thisArg || vjs, array[i], i, array); - } +var assignHasPendingExceptions = function () { + if (!Object.assign || !Object.preventExtensions) { + return false; + } + // Firefox 37 still has "pending exception" logic in its Object.assign implementation, + // which is 72% slower than our shim, and Firefox 40's native implementation. + var thrower = Object.preventExtensions({ 1: 2 }); + try { + Object.assign(thrower, 'xy'); + } catch (e) { + return thrower[1] === 'y'; } - - return array; }; -/** - * Simple http request for retrieving external files (e.g. text tracks) - * - * ##### Example - * - * // using url string - * videojs.xhr('http://example.com/myfile.vtt', function(error, response, responseBody){}); - * - * // or options block - * videojs.xhr({ - * uri: 'http://example.com/myfile.vtt', - * method: 'GET', - * responseType: 'text' - * }, function(error, response, responseBody){ - * if (error) { - * // log the error - * } else { - * // successful, do something with the response - * } - * }); - * - * - * API is modeled after the Raynos/xhr, which we hope to use after - * getting browserify implemented. - * https://github.com/Raynos/xhr/blob/master/index.js - * - * @param {Object|String} options Options block or URL string - * @param {Function} callback The callback function - * @returns {Object} The request - */ -vjs.xhr = function(options, callback){ - var XHR, request, urlInfo, winLoc, fileUrl, crossOrigin, abortTimeout, successHandler, errorHandler; - // If options is a string it's the url - if (typeof options === 'string') { - options = { - uri: options - }; +module.exports = function getPolyfill() { + if (!Object.assign) { + return implementation; } + if (lacksProperEnumerationOrder()) { + return implementation; + } + if (assignHasPendingExceptions()) { + return implementation; + } + return Object.assign; +}; - // Merge with default options - videojs.util.mergeOptions({ - method: 'GET', - timeout: 45 * 1000 - }, options); - - callback = callback || function(){}; - - successHandler = function(){ - window.clearTimeout(abortTimeout); - callback(null, request, request.response || request.responseText); - }; +},{"./implementation":52}],55:[function(_dereq_,module,exports){ +'use strict'; - errorHandler = function(err){ - window.clearTimeout(abortTimeout); +var define = _dereq_('define-properties'); +var getPolyfill = _dereq_('./polyfill'); - if (!err || typeof err === 'string') { - err = new Error(err); - } +module.exports = function shimAssign() { + var polyfill = getPolyfill(); + define( + Object, + { assign: polyfill }, + { assign: function () { return Object.assign !== polyfill; } } + ); + return polyfill; +}; - callback(err, request); - }; +},{"./polyfill":54,"define-properties":2}],56:[function(_dereq_,module,exports){ +module.exports = once - XHR = window.XMLHttpRequest; +once.proto = once(function () { + Object.defineProperty(Function.prototype, 'once', { + value: function () { + return once(this) + }, + configurable: true + }) +}) - if (typeof XHR === 'undefined') { - // Shim XMLHttpRequest for older IEs - XHR = function () { - try { return new window.ActiveXObject('Msxml2.XMLHTTP.6.0'); } catch (e) {} - try { return new window.ActiveXObject('Msxml2.XMLHTTP.3.0'); } catch (f) {} - try { return new window.ActiveXObject('Msxml2.XMLHTTP'); } catch (g) {} - throw new Error('This browser does not support XMLHttpRequest.'); - }; +function once (fn) { + var called = false + return function () { + if (called) return + called = true + return fn.apply(this, arguments) } +} - request = new XHR(); - // Store a reference to the url on the request instance - request.uri = options.uri; +},{}],57:[function(_dereq_,module,exports){ +var trim = _dereq_('trim') + , forEach = _dereq_('for-each') + , isArray = function(arg) { + return Object.prototype.toString.call(arg) === '[object Array]'; + } - urlInfo = vjs.parseUrl(options.uri); - winLoc = window.location; - // Check if url is for another domain/origin - // IE8 doesn't know location.origin, so we won't rely on it here - crossOrigin = (urlInfo.protocol + urlInfo.host) !== (winLoc.protocol + winLoc.host); - - // XDomainRequest -- Use for IE if XMLHTTPRequest2 isn't available - // 'withCredentials' is only available in XMLHTTPRequest2 - // Also XDomainRequest has a lot of gotchas, so only use if cross domain - if (crossOrigin && window.XDomainRequest && !('withCredentials' in request)) { - request = new window.XDomainRequest(); - request.onload = successHandler; - request.onerror = errorHandler; - // These blank handlers need to be set to fix ie9 - // http://cypressnorth.com/programming/internet-explorer-aborting-ajax-requests-fixed/ - request.onprogress = function(){}; - request.ontimeout = function(){}; - - // XMLHTTPRequest - } else { - fileUrl = (urlInfo.protocol == 'file:' || winLoc.protocol == 'file:'); +module.exports = function (headers) { + if (!headers) + return {} - request.onreadystatechange = function() { - if (request.readyState === 4) { - if (request.timedout) { - return errorHandler('timeout'); - } + var result = {} + + forEach( + trim(headers).split('\n') + , function (row) { + var index = row.indexOf(':') + , key = trim(row.slice(0, index)).toLowerCase() + , value = trim(row.slice(index + 1)) - if (request.status === 200 || fileUrl && request.status === 0) { - successHandler(); + if (typeof(result[key]) === 'undefined') { + result[key] = value + } else if (isArray(result[key])) { + result[key].push(value) } else { - errorHandler(); + result[key] = [ result[key], value ] } } - }; + ) - if (options.timeout) { - abortTimeout = window.setTimeout(function() { - if (request.readyState !== 4) { - request.timedout = true; - request.abort(); - } - }, options.timeout); + return result +} +},{"for-each":3,"trim":59}],58:[function(_dereq_,module,exports){ +module.exports = SafeParseTuple + +function SafeParseTuple(obj, reviver) { + var json + var error = null + + try { + json = JSON.parse(obj, reviver) + } catch (err) { + error = err } - } - // open the connection - try { - // Third arg is async, or ignored by XDomainRequest - request.open(options.method || 'GET', options.uri, true); - } catch(err) { - return errorHandler(err); - } + return [error, json] +} - // withCredentials only supported by XMLHttpRequest2 - if(options.withCredentials) { - request.withCredentials = true; - } +},{}],59:[function(_dereq_,module,exports){ - if (options.responseType) { - request.responseType = options.responseType; - } +exports = module.exports = trim; - // send the request - try { - request.send(); - } catch(err) { - return errorHandler(err); - } +function trim(str){ + return str.replace(/^\s*|\s*$/g, ''); +} - return request; +exports.left = function(str){ + return str.replace(/^\s*/, ''); }; -/** - * Utility functions namespace - * @namespace - * @type {Object} - */ -vjs.util = {}; -/** - * Merge two options objects, recursively merging any plain object properties as - * well. Previously `deepMerge` - * - * @param {Object} obj1 Object to override values in - * @param {Object} obj2 Overriding object - * @return {Object} New object -- obj1 and obj2 will be untouched - */ -vjs.util.mergeOptions = function(obj1, obj2){ - var key, val1, val2; +exports.right = function(str){ + return str.replace(/\s*$/, ''); +}; - // make a copy of obj1 so we're not overwriting original values. - // like prototype.options_ and all sub options objects - obj1 = vjs.obj.copy(obj1); +},{}],60:[function(_dereq_,module,exports){ +function clean (s) { + return s.replace(/\n\r?\s*/g, '') +} - for (key in obj2){ - if (obj2.hasOwnProperty(key)) { - val1 = obj1[key]; - val2 = obj2[key]; - // Check if both properties are pure objects and do a deep merge if so - if (vjs.obj.isPlain(val1) && vjs.obj.isPlain(val2)) { - obj1[key] = vjs.util.mergeOptions(val1, val2); - } else { - obj1[key] = obj2[key]; - } +module.exports = function tsml (sa) { + var s = '' + , i = 0 + + for (; i < arguments.length; i++) + s += clean(sa[i]) + (arguments[i + 1] || '') + + return s +} +},{}],61:[function(_dereq_,module,exports){ +"use strict"; +var window = _dereq_("global/window") +var once = _dereq_("once") +var isFunction = _dereq_("is-function") +var parseHeaders = _dereq_("parse-headers") +var xtend = _dereq_("xtend") + +module.exports = createXHR +createXHR.XMLHttpRequest = window.XMLHttpRequest || noop +createXHR.XDomainRequest = "withCredentials" in (new createXHR.XMLHttpRequest()) ? createXHR.XMLHttpRequest : window.XDomainRequest + +forEachArray(["get", "put", "post", "patch", "head", "delete"], function(method) { + createXHR[method === "delete" ? "del" : method] = function(uri, options, callback) { + options = initParams(uri, options, callback) + options.method = method.toUpperCase() + return _createXHR(options) } - } - return obj1; -};vjs.EventEmitter = function() { -}; +}) -vjs.EventEmitter.prototype.allowedEvents_ = { -}; +function forEachArray(array, iterator) { + for (var i = 0; i < array.length; i++) { + iterator(array[i]) + } +} -vjs.EventEmitter.prototype.on = function(type, fn) { - // Remove the addEventListener alias before calling vjs.on - // so we don't get into an infinite type loop - var ael = this.addEventListener; - this.addEventListener = Function.prototype; - vjs.on(this, type, fn); - this.addEventListener = ael; -}; -vjs.EventEmitter.prototype.addEventListener = vjs.EventEmitter.prototype.on; +function isEmpty(obj){ + for(var i in obj){ + if(obj.hasOwnProperty(i)) return false + } + return true +} -vjs.EventEmitter.prototype.off = function(type, fn) { - vjs.off(this, type, fn); -}; -vjs.EventEmitter.prototype.removeEventListener = vjs.EventEmitter.prototype.off; +function initParams(uri, options, callback) { + var params = uri -vjs.EventEmitter.prototype.one = function(type, fn) { - vjs.one(this, type, fn); -}; + if (isFunction(options)) { + callback = options + if (typeof uri === "string") { + params = {uri:uri} + } + } else { + params = xtend(options, {uri: uri}) + } -vjs.EventEmitter.prototype.trigger = function(event) { - var type = event.type || event; + params.callback = callback + return params +} - if (typeof event === 'string') { - event = { - type: type - }; - } - event = vjs.fixEvent(event); +function createXHR(uri, options, callback) { + options = initParams(uri, options, callback) + return _createXHR(options) +} - if (this.allowedEvents_[type] && this['on' + type]) { - this['on' + type](event); - } +function _createXHR(options) { + var callback = options.callback + if(typeof callback === "undefined"){ + throw new Error("callback argument missing") + } + callback = once(callback) - vjs.trigger(this, event); -}; -// The standard DOM EventTarget.dispatchEvent() is aliased to trigger() -vjs.EventEmitter.prototype.dispatchEvent = vjs.EventEmitter.prototype.trigger; -/** - * @fileoverview Player Component - Base class for all UI objects - * - */ + function readystatechange() { + if (xhr.readyState === 4) { + loadFunc() + } + } -/** - * Base UI Component class - * - * Components are embeddable UI objects that are represented by both a - * javascript object and an element in the DOM. They can be children of other - * components, and can have many children themselves. - * - * // adding a button to the player - * var button = player.addChild('button'); - * button.el(); // -> button element - * - *
- *
Button
- *
- * - * Components are also event emitters. - * - * button.on('click', function(){ - * console.log('Button Clicked!'); - * }); - * - * button.trigger('customevent'); - * - * @param {Object} player Main Player - * @param {Object=} options - * @class - * @constructor - * @extends vjs.CoreObject - */ -vjs.Component = vjs.CoreObject.extend({ - /** - * the constructor function for the class - * - * @constructor - */ - init: function(player, options, ready){ - this.player_ = player; + function getBody() { + // Chrome with requestType=blob throws errors arround when even testing access to responseText + var body = undefined - // Make a copy of prototype.options_ to protect against overriding global defaults - this.options_ = vjs.obj.copy(this.options_); + if (xhr.response) { + body = xhr.response + } else if (xhr.responseType === "text" || !xhr.responseType) { + body = xhr.responseText || xhr.responseXML + } - // Updated options with supplied options - options = this.options(options); + if (isJson) { + try { + body = JSON.parse(body) + } catch (e) {} + } - // Get ID from options or options element if one is supplied - this.id_ = options['id'] || (options['el'] && options['el']['id']); + return body + } - // If there was no ID from the options, generate one - if (!this.id_) { - // Don't require the player ID function in the case of mock players - this.id_ = ((player.id && player.id()) || 'no_player') + '_component_' + vjs.guid++; + var failureResponse = { + body: undefined, + headers: {}, + statusCode: 0, + method: method, + url: uri, + rawRequest: xhr + } + + function errorFunc(evt) { + clearTimeout(timeoutTimer) + if(!(evt instanceof Error)){ + evt = new Error("" + (evt || "Unknown XMLHttpRequest Error") ) + } + evt.statusCode = 0 + callback(evt, failureResponse) } - this.name_ = options['name'] || null; + // will load the data & process the response in a special response object + function loadFunc() { + if (aborted) return + var status + clearTimeout(timeoutTimer) + if(options.useXDR && xhr.status===undefined) { + //IE8 CORS GET successful response doesn't have a status field, but body is fine + status = 200 + } else { + status = (xhr.status === 1223 ? 204 : xhr.status) + } + var response = failureResponse + var err = null + + if (status !== 0){ + response = { + body: getBody(), + statusCode: status, + method: method, + headers: {}, + url: uri, + rawRequest: xhr + } + if(xhr.getAllResponseHeaders){ //remember xhr can in fact be XDR for CORS in IE + response.headers = parseHeaders(xhr.getAllResponseHeaders()) + } + } else { + err = new Error("Internal XMLHttpRequest Error") + } + callback(err, response, response.body) - // Create element if one wasn't provided in options - this.el_ = options['el'] || this.createEl(); + } - this.children_ = []; - this.childIndex_ = {}; - this.childNameIndex_ = {}; + var xhr = options.xhr || null - // Add any child components in options - this.initChildren(); + if (!xhr) { + if (options.cors || options.useXDR) { + xhr = new createXHR.XDomainRequest() + }else{ + xhr = new createXHR.XMLHttpRequest() + } + } - this.ready(ready); - // Don't want to trigger ready here or it will before init is actually - // finished for all children that run this constructor + var key + var aborted + var uri = xhr.url = options.uri || options.url + var method = xhr.method = options.method || "GET" + var body = options.body || options.data || null + var headers = xhr.headers = options.headers || {} + var sync = !!options.sync + var isJson = false + var timeoutTimer + + if ("json" in options) { + isJson = true + headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json") //Don't override existing accept header declared by user + if (method !== "GET" && method !== "HEAD") { + headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json") //Don't override existing accept header declared by user + body = JSON.stringify(options.json) + } + } - if (options.reportTouchActivity !== false) { - this.enableTouchActivity(); + xhr.onreadystatechange = readystatechange + xhr.onload = loadFunc + xhr.onerror = errorFunc + // IE9 must have onprogress be set to a unique function. + xhr.onprogress = function () { + // IE must die + } + xhr.ontimeout = errorFunc + xhr.open(method, uri, !sync, options.username, options.password) + //has to be after open + if(!sync) { + xhr.withCredentials = !!options.withCredentials + } + // Cannot set timeout with sync request + // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly + // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent + if (!sync && options.timeout > 0 ) { + timeoutTimer = setTimeout(function(){ + aborted=true//IE9 may still call readystatechange + xhr.abort("timeout") + var e = new Error("XMLHttpRequest timeout") + e.code = "ETIMEDOUT" + errorFunc(e) + }, options.timeout ) } - } -}); -/** - * Dispose of the component and all child components - */ -vjs.Component.prototype.dispose = function(){ - this.trigger({ type: 'dispose', 'bubbles': false }); + if (xhr.setRequestHeader) { + for(key in headers){ + if(headers.hasOwnProperty(key)){ + xhr.setRequestHeader(key, headers[key]) + } + } + } else if (options.headers && !isEmpty(options.headers)) { + throw new Error("Headers cannot be set on an XDomainRequest object") + } - // Dispose all children. - if (this.children_) { - for (var i = this.children_.length - 1; i >= 0; i--) { - if (this.children_[i].dispose) { - this.children_[i].dispose(); - } + if ("responseType" in options) { + xhr.responseType = options.responseType } - } - // Delete child references - this.children_ = null; - this.childIndex_ = null; - this.childNameIndex_ = null; + if ("beforeSend" in options && + typeof options.beforeSend === "function" + ) { + options.beforeSend(xhr) + } - // Remove all event listeners. - this.off(); + xhr.send(body) - // Remove element from DOM - if (this.el_.parentNode) { - this.el_.parentNode.removeChild(this.el_); - } + return xhr - vjs.removeData(this.el_); - this.el_ = null; -}; -/** - * Reference to main player instance - * - * @type {vjs.Player} - * @private - */ -vjs.Component.prototype.player_ = true; +} -/** - * Return the component's player - * - * @return {vjs.Player} - */ -vjs.Component.prototype.player = function(){ - return this.player_; -}; +function noop() {} -/** - * The component's options object - * - * @type {Object} - * @private - */ -vjs.Component.prototype.options_; +},{"global/window":8,"is-function":9,"once":56,"parse-headers":57,"xtend":62}],62:[function(_dereq_,module,exports){ +module.exports = extend -/** - * Deep merge of options objects - * - * Whenever a property is an object on both options objects - * the two properties will be merged using vjs.obj.deepMerge. - * - * This is used for merging options for child components. We - * want it to be easy to override individual options on a child - * component without having to rewrite all the other default options. - * - * Parent.prototype.options_ = { - * children: { - * 'childOne': { 'foo': 'bar', 'asdf': 'fdsa' }, - * 'childTwo': {}, - * 'childThree': {} - * } - * } - * newOptions = { - * children: { - * 'childOne': { 'foo': 'baz', 'abc': '123' } - * 'childTwo': null, - * 'childFour': {} - * } - * } - * - * this.options(newOptions); - * - * RESULT - * - * { - * children: { - * 'childOne': { 'foo': 'baz', 'asdf': 'fdsa', 'abc': '123' }, - * 'childTwo': null, // Disabled. Won't be initialized. - * 'childThree': {}, - * 'childFour': {} - * } - * } - * - * @param {Object} obj Object of new option values - * @return {Object} A NEW object of this.options_ and obj merged - */ -vjs.Component.prototype.options = function(obj){ - if (obj === undefined) return this.options_; +var hasOwnProperty = Object.prototype.hasOwnProperty; - return this.options_ = vjs.util.mergeOptions(this.options_, obj); -}; +function extend() { + var target = {} + + for (var i = 0; i < arguments.length; i++) { + var source = arguments[i] + + for (var key in source) { + if (hasOwnProperty.call(source, key)) { + target[key] = source[key] + } + } + } + + return target +} +},{}],63:[function(_dereq_,module,exports){ /** - * The DOM element for the component - * - * @type {Element} - * @private + * @file big-play-button.js */ -vjs.Component.prototype.el_; +'use strict'; -/** - * Create the component's DOM element - * - * @param {String=} tagName Element's node type. e.g. 'div' - * @param {Object=} attributes An object of element attributes that should be set on the element - * @return {Element} - */ -vjs.Component.prototype.createEl = function(tagName, attributes){ - return vjs.createEl(tagName, attributes); -}; +exports.__esModule = true; -vjs.Component.prototype.localize = function(string){ - var lang = this.player_.language(), - languages = this.player_.languages(); - if (languages && languages[lang] && languages[lang][string]) { - return languages[lang][string]; - } - return string; -}; +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } -/** - * Get the component's DOM element - * - * var domEl = myComponent.el(); - * - * @return {Element} - */ -vjs.Component.prototype.el = function(){ - return this.el_; -}; +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } -/** - * An optional element where, if defined, children will be inserted instead of - * directly in `el_` - * - * @type {Element} - * @private - */ -vjs.Component.prototype.contentEl_; +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } -/** - * Return the component's DOM element for embedding content. - * Will either be el_ or a new element defined in createEl. - * - * @return {Element} - */ -vjs.Component.prototype.contentEl = function(){ - return this.contentEl_ || this.el_; -}; +var _buttonJs = _dereq_('./button.js'); -/** - * The ID for the component - * - * @type {String} - * @private - */ -vjs.Component.prototype.id_; +var _buttonJs2 = _interopRequireDefault(_buttonJs); -/** - * Get the component's ID - * - * var id = myComponent.id(); - * - * @return {String} - */ -vjs.Component.prototype.id = function(){ - return this.id_; -}; +var _componentJs = _dereq_('./component.js'); -/** - * The name for the component. Often used to reference the component. - * - * @type {String} - * @private - */ -vjs.Component.prototype.name_; +var _componentJs2 = _interopRequireDefault(_componentJs); /** - * Get the component's name. The name is often used to reference the component. - * - * var name = myComponent.name(); + * Initial play button. Shows before the video has played. The hiding of the + * big play button is done via CSS and player states. * - * @return {String} + * @param {Object} player Main Player + * @param {Object=} options Object of option names and values + * @extends Button + * @class BigPlayButton */ -vjs.Component.prototype.name = function(){ - return this.name_; -}; -/** - * Array of child components - * - * @type {Array} - * @private - */ -vjs.Component.prototype.children_; +var BigPlayButton = (function (_Button) { + _inherits(BigPlayButton, _Button); -/** - * Get an array of all child components - * - * var kids = myComponent.children(); - * - * @return {Array} The children - */ -vjs.Component.prototype.children = function(){ - return this.children_; -}; + function BigPlayButton(player, options) { + _classCallCheck(this, BigPlayButton); -/** - * Object of child components by ID - * - * @type {Object} - * @private - */ -vjs.Component.prototype.childIndex_; + _Button.call(this, player, options); + } -/** - * Returns a child component with the provided ID - * - * @return {vjs.Component} - */ -vjs.Component.prototype.getChildById = function(id){ - return this.childIndex_[id]; -}; + /** + * Allow sub components to stack CSS class names + * + * @return {String} The constructed class name + * @method buildCSSClass + */ -/** - * Object of child components by name - * - * @type {Object} - * @private - */ -vjs.Component.prototype.childNameIndex_; + BigPlayButton.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-big-play-button'; + }; -/** - * Returns a child component with the provided name - * - * @return {vjs.Component} - */ -vjs.Component.prototype.getChild = function(name){ - return this.childNameIndex_[name]; -}; + /** + * Handles click for play + * + * @method handleClick + */ + BigPlayButton.prototype.handleClick = function handleClick() { + this.player_.play(); + }; + + return BigPlayButton; +})(_buttonJs2['default']); + +BigPlayButton.prototype.controlText_ = 'Play Video'; + +_componentJs2['default'].registerComponent('BigPlayButton', BigPlayButton); +exports['default'] = BigPlayButton; +module.exports = exports['default']; + +},{"./button.js":64,"./component.js":67}],64:[function(_dereq_,module,exports){ /** - * Adds a child component inside this component - * - * myComponent.el(); - * // ->
- * myComonent.children(); - * // [empty array] - * - * var myButton = myComponent.addChild('MyButton'); - * // ->
myButton
- * // -> myButton === myComonent.children()[0]; - * - * Pass in options for child constructors and options for children of the child - * - * var myButton = myComponent.addChild('MyButton', { - * text: 'Press Me', - * children: { - * buttonChildExample: { - * buttonChildOption: true - * } - * } - * }); - * - * @param {String|vjs.Component} child The class name or instance of a child to add - * @param {Object=} options Options, including options to be passed to children of the child. - * @return {vjs.Component} The child component (created by this process if a string was used) - * @suppress {accessControls|checkRegExp|checkTypes|checkVars|const|constantProperty|deprecated|duplicate|es5Strict|fileoverviewTags|globalThis|invalidCasts|missingProperties|nonStandardJsDocs|strictModuleDepCheck|undefinedNames|undefinedVars|unknownDefines|uselessCode|visibility} + * @file button.js */ -vjs.Component.prototype.addChild = function(child, options){ - var component, componentClass, componentName; +'use strict'; - // If child is a string, create new component with options - if (typeof child === 'string') { - componentName = child; +exports.__esModule = true; - // Make sure options is at least an empty object to protect against errors - options = options || {}; +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - // If no componentClass in options, assume componentClass is the name lowercased - // (e.g. playButton) - componentClass = options['componentClass'] || vjs.capitalize(componentName); +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - // Set name through options - options['name'] = componentName; +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - // Create a new object & element for this controls set - // If there's no .player_, this is a player - // Closure Compiler throws an 'incomplete alias' warning if we use the vjs variable directly. - // Every class should be exported, so this should never be a problem here. - component = new window['videojs'][componentClass](this.player_ || this, options); +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - // child is a component instance - } else { - component = child; - } +var _clickableComponentJs = _dereq_('./clickable-component.js'); - this.children_.push(component); +var _clickableComponentJs2 = _interopRequireDefault(_clickableComponentJs); - if (typeof component.id === 'function') { - this.childIndex_[component.id()] = component; - } +var _component = _dereq_('./component'); - // If a name wasn't used to create the component, check if we can use the - // name function of the component - componentName = componentName || (component.name && component.name()); +var _component2 = _interopRequireDefault(_component); - if (componentName) { - this.childNameIndex_[componentName] = component; - } +var _utilsEventsJs = _dereq_('./utils/events.js'); - // Add the UI object's element to the container div (box) - // Having an element is not required - if (typeof component['el'] === 'function' && component['el']()) { - this.contentEl().appendChild(component['el']()); - } +var Events = _interopRequireWildcard(_utilsEventsJs); - // Return so it can stored on parent object if desired. - return component; -}; +var _utilsFnJs = _dereq_('./utils/fn.js'); -/** - * Remove a child component from this component's list of children, and the - * child component's element from this component's element - * - * @param {vjs.Component} component Component to remove - */ -vjs.Component.prototype.removeChild = function(component){ - if (typeof component === 'string') { - component = this.getChild(component); - } +var Fn = _interopRequireWildcard(_utilsFnJs); - if (!component || !this.children_) return; +var _utilsLogJs = _dereq_('./utils/log.js'); - var childFound = false; - for (var i = this.children_.length - 1; i >= 0; i--) { - if (this.children_[i] === component) { - childFound = true; - this.children_.splice(i,1); - break; - } - } +var _utilsLogJs2 = _interopRequireDefault(_utilsLogJs); - if (!childFound) return; +var _globalDocument = _dereq_('global/document'); - this.childIndex_[component.id()] = null; - this.childNameIndex_[component.name()] = null; +var _globalDocument2 = _interopRequireDefault(_globalDocument); - var compEl = component.el(); - if (compEl && compEl.parentNode === this.contentEl()) { - this.contentEl().removeChild(component.el()); - } -}; +var _objectAssign = _dereq_('object.assign'); + +var _objectAssign2 = _interopRequireDefault(_objectAssign); /** - * Add and initialize default child components from options - * - * // when an instance of MyComponent is created, all children in options - * // will be added to the instance by their name strings and options - * MyComponent.prototype.options_.children = { - * myChildComponent: { - * myChildOption: true - * } - * } - * - * // Or when creating the component - * var myComp = new MyComponent(player, { - * children: { - * myChildComponent: { - * myChildOption: true - * } - * } - * }); - * - * The children option can also be an Array of child names or - * child options objects (that also include a 'name' key). - * - * var myComp = new MyComponent(player, { - * children: [ - * 'button', - * { - * name: 'button', - * someOtherOption: true - * } - * ] - * }); + * Base class for all buttons * + * @param {Object} player Main Player + * @param {Object=} options Object of option names and values + * @extends ClickableComponent + * @class Button */ -vjs.Component.prototype.initChildren = function(){ - var parent, parentOptions, children, child, name, opts, handleAdd; - parent = this; - parentOptions = parent.options(); - children = parentOptions['children']; +var Button = (function (_ClickableComponent) { + _inherits(Button, _ClickableComponent); - if (children) { - handleAdd = function(name, opts){ - // Allow options for children to be set at the parent options - // e.g. videojs(id, { controlBar: false }); - // instead of videojs(id, { children: { controlBar: false }); - if (parentOptions[name] !== undefined) { - opts = parentOptions[name]; - } + function Button(player, options) { + _classCallCheck(this, Button); - // Allow for disabling default components - // e.g. vjs.options['children']['posterImage'] = false - if (opts === false) return; + _ClickableComponent.call(this, player, options); + } - // Create and add the child component. - // Add a direct reference to the child by name on the parent instance. - // If two of the same component are used, different names should be supplied - // for each - parent[name] = parent.addChild(name, opts); - }; + /** + * Create the component's DOM element + * + * @param {String=} type Element's node type. e.g. 'div' + * @param {Object=} props An object of properties that should be set on the element + * @param {Object=} attributes An object of attributes that should be set on the element + * @return {Element} + * @method createEl + */ - // Allow for an array of children details to passed in the options - if (vjs.obj.isArray(children)) { - for (var i = 0; i < children.length; i++) { - child = children[i]; + Button.prototype.createEl = function createEl() { + var tag = arguments.length <= 0 || arguments[0] === undefined ? 'button' : arguments[0]; + var props = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + var attributes = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; - if (typeof child == 'string') { - // ['myComponent'] - name = child; - opts = {}; - } else { - // [{ name: 'myComponent', otherOption: true }] - name = child.name; - opts = child; - } + props = _objectAssign2['default']({ + className: this.buildCSSClass() + }, props); - handleAdd(name, opts); - } - } else { - vjs.obj.each(children, handleAdd); + if (tag !== 'button') { + _utilsLogJs2['default'].warn('Creating a Button with an HTML element of ' + tag + ' is deprecated; use ClickableComponent instead.'); + + // Add properties for clickable element which is not a native HTML button + props = _objectAssign2['default']({ + tabIndex: 0 + }, props); + + // Add ARIA attributes for clickable element which is not a native HTML button + attributes = _objectAssign2['default']({ + role: 'button' + }, attributes); } - } -}; -/** - * Allows sub components to stack CSS class names - * - * @return {String} The constructed class name - */ -vjs.Component.prototype.buildCSSClass = function(){ - // Child classes can include a function that does: - // return 'CLASS NAME' + this._super(); - return ''; -}; + // Add attributes for button element + attributes = _objectAssign2['default']({ + type: 'button', // Necessary since the default button type is "submit" + 'aria-live': 'polite' // let the screen reader user know that the text of the button may change + }, attributes); -/* Events -============================================================================= */ + var el = _component2['default'].prototype.createEl.call(this, tag, props, attributes); -/** - * Add an event listener to this component's element - * - * var myFunc = function(){ - * var myComponent = this; - * // Do something when the event is fired - * }; - * - * myComponent.on('eventType', myFunc); - * - * The context of myFunc will be myComponent unless previously bound. - * - * Alternatively, you can add a listener to another element or component. - * - * myComponent.on(otherElement, 'eventName', myFunc); - * myComponent.on(otherComponent, 'eventName', myFunc); - * - * The benefit of using this over `vjs.on(otherElement, 'eventName', myFunc)` - * and `otherComponent.on('eventName', myFunc)` is that this way the listeners - * will be automatically cleaned up when either component is disposed. - * It will also bind myComponent as the context of myFunc. - * - * **NOTE**: When using this on elements in the page other than window - * and document (both permanent), if you remove the element from the DOM - * you need to call `vjs.trigger(el, 'dispose')` on it to clean up - * references to it and allow the browser to garbage collect it. - * - * @param {String|vjs.Component} first The event type or other component - * @param {Function|String} second The event handler or event type - * @param {Function} third The event handler - * @return {vjs.Component} self - */ -vjs.Component.prototype.on = function(first, second, third){ - var target, type, fn, removeOnDispose, cleanRemover, thisComponent; + this.createControlTextEl(el); - if (typeof first === 'string' || vjs.obj.isArray(first)) { - vjs.on(this.el_, first, vjs.bind(this, second)); + return el; + }; - // Targeting another component or element - } else { - target = first; - type = second; - fn = vjs.bind(this, third); - thisComponent = this; - - // When this component is disposed, remove the listener from the other component - removeOnDispose = function(){ - thisComponent.off(target, type, fn); - }; - // Use the same function ID so we can remove it later it using the ID - // of the original listener - removeOnDispose.guid = fn.guid; - this.on('dispose', removeOnDispose); - - // If the other component is disposed first we need to clean the reference - // to the other component in this component's removeOnDispose listener - // Otherwise we create a memory leak. - cleanRemover = function(){ - thisComponent.off('dispose', removeOnDispose); - }; - // Add the same function ID so we can easily remove it later - cleanRemover.guid = fn.guid; + /** + * Adds a child component inside this button + * + * @param {String|Component} child The class name or instance of a child to add + * @param {Object=} options Options, including options to be passed to children of the child. + * @return {Component} The child component (created by this process if a string was used) + * @deprecated + * @method addChild + */ - // Check if this is a DOM node - if (first.nodeName) { - // Add the listener to the other element - vjs.on(target, type, fn); - vjs.on(target, 'dispose', cleanRemover); + Button.prototype.addChild = function addChild(child) { + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; - // Should be a component - // Not using `instanceof vjs.Component` because it makes mock players difficult - } else if (typeof first.on === 'function') { - // Add the listener to the other component - target.on(type, fn); - target.on('dispose', cleanRemover); - } - } + var className = this.constructor.name; + _utilsLogJs2['default'].warn('Adding an actionable (user controllable) child to a Button (' + className + ') is not supported; use a ClickableComponent instead.'); - return this; -}; + // Avoid the error message generated by ClickableComponent's addChild method + return _component2['default'].prototype.addChild.call(this, child, options); + }; -/** - * Remove an event listener from this component's element - * - * myComponent.off('eventType', myFunc); - * - * If myFunc is excluded, ALL listeners for the event type will be removed. - * If eventType is excluded, ALL listeners will be removed from the component. - * - * Alternatively you can use `off` to remove listeners that were added to other - * elements or components using `myComponent.on(otherComponent...`. - * In this case both the event type and listener function are REQUIRED. - * - * myComponent.off(otherElement, 'eventType', myFunc); - * myComponent.off(otherComponent, 'eventType', myFunc); - * - * @param {String=|vjs.Component} first The event type or other component - * @param {Function=|String} second The listener function or event type - * @param {Function=} third The listener for other component - * @return {vjs.Component} - */ -vjs.Component.prototype.off = function(first, second, third){ - var target, otherComponent, type, fn, otherEl; + /** + * Handle KeyPress (document level) - Extend with specific functionality for button + * + * @method handleKeyPress + */ - if (!first || typeof first === 'string' || vjs.obj.isArray(first)) { - vjs.off(this.el_, first, second); - } else { - target = first; - type = second; - // Ensure there's at least a guid, even if the function hasn't been used - fn = vjs.bind(this, third); - - // Remove the dispose listener on this component, - // which was given the same guid as the event listener - this.off('dispose', fn); - - if (first.nodeName) { - // Remove the listener - vjs.off(target, type, fn); - // Remove the listener for cleaning the dispose listener - vjs.off(target, 'dispose', fn); - } else { - target.off(type, fn); - target.off('dispose', fn); + Button.prototype.handleKeyPress = function handleKeyPress(event) { + // Ignore Space (32) or Enter (13) key operation, which is handled by the browser for a button. + if (event.which === 32 || event.which === 13) {} else { + _ClickableComponent.prototype.handleKeyPress.call(this, event); // Pass keypress handling up for unsupported keys } - } + }; - return this; -}; + return Button; +})(_clickableComponentJs2['default']); + +_component2['default'].registerComponent('Button', Button); +exports['default'] = Button; +module.exports = exports['default']; +},{"./clickable-component.js":65,"./component":67,"./utils/events.js":143,"./utils/fn.js":144,"./utils/log.js":147,"global/document":7,"object.assign":53}],65:[function(_dereq_,module,exports){ /** - * Add an event listener to be triggered only once and then removed - * - * myComponent.one('eventName', myFunc); - * - * Alternatively you can add a listener to another element or component - * that will be triggered only once. - * - * myComponent.one(otherElement, 'eventName', myFunc); - * myComponent.one(otherComponent, 'eventName', myFunc); - * - * @param {String|vjs.Component} first The event type or other component - * @param {Function|String} second The listener function or event type - * @param {Function=} third The listener function for other component - * @return {vjs.Component} + * @file button.js */ -vjs.Component.prototype.one = function(first, second, third) { - var target, type, fn, thisComponent, newFunc; +'use strict'; - if (typeof first === 'string' || vjs.obj.isArray(first)) { - vjs.one(this.el_, first, vjs.bind(this, second)); - } else { - target = first; - type = second; - fn = vjs.bind(this, third); - thisComponent = this; - - newFunc = function(){ - thisComponent.off(target, type, newFunc); - fn.apply(this, arguments); - }; - // Keep the same function ID so we can remove it later - newFunc.guid = fn.guid; +exports.__esModule = true; - this.on(target, type, newFunc); - } +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - return this; -}; +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } -/** - * Trigger an event on an element - * - * myComponent.trigger('eventName'); - * myComponent.trigger({'type':'eventName'}); - * - * @param {Event|Object|String} event A string (the type) or an event object with a type attribute - * @return {vjs.Component} self - */ -vjs.Component.prototype.trigger = function(event){ - vjs.trigger(this.el_, event); - return this; -}; +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } -/* Ready -================================================================================ */ -/** - * Is the component loaded - * This can mean different things depending on the component. - * - * @private - * @type {Boolean} - */ -vjs.Component.prototype.isReady_; +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } -/** - * Trigger ready as soon as initialization is finished - * - * Allows for delaying ready. Override on a sub class prototype. - * If you set this.isReadyOnInitFinish_ it will affect all components. - * Specially used when waiting for the Flash player to asynchronously load. - * - * @type {Boolean} - * @private - */ -vjs.Component.prototype.isReadyOnInitFinish_ = true; +var _component = _dereq_('./component'); -/** - * List of ready listeners - * - * @type {Array} - * @private - */ -vjs.Component.prototype.readyQueue_; +var _component2 = _interopRequireDefault(_component); -/** - * Bind a listener to the component's ready state - * - * Different from event listeners in that if the ready event has already happened - * it will trigger the function immediately. - * - * @param {Function} fn Ready listener - * @return {vjs.Component} - */ -vjs.Component.prototype.ready = function(fn){ - if (fn) { - if (this.isReady_) { - fn.call(this); - } else { - if (this.readyQueue_ === undefined) { - this.readyQueue_ = []; - } - this.readyQueue_.push(fn); - } - } - return this; -}; +var _utilsDomJs = _dereq_('./utils/dom.js'); -/** - * Trigger the ready listeners - * - * @return {vjs.Component} - */ -vjs.Component.prototype.triggerReady = function(){ - this.isReady_ = true; +var Dom = _interopRequireWildcard(_utilsDomJs); - var readyQueue = this.readyQueue_; +var _utilsEventsJs = _dereq_('./utils/events.js'); - // Reset Ready Queue - this.readyQueue_ = []; +var Events = _interopRequireWildcard(_utilsEventsJs); - if (readyQueue && readyQueue.length > 0) { +var _utilsFnJs = _dereq_('./utils/fn.js'); - for (var i = 0, j = readyQueue.length; i < j; i++) { - readyQueue[i].call(this); - } +var Fn = _interopRequireWildcard(_utilsFnJs); - // Allow for using event listeners also, in case you want to do something everytime a source is ready. - this.trigger('ready'); - } -}; +var _utilsLogJs = _dereq_('./utils/log.js'); -/* Display -============================================================================= */ +var _utilsLogJs2 = _interopRequireDefault(_utilsLogJs); -/** - * Check if a component's element has a CSS class name - * - * @param {String} classToCheck Classname to check - * @return {vjs.Component} - */ -vjs.Component.prototype.hasClass = function(classToCheck){ - return vjs.hasClass(this.el_, classToCheck); -}; +var _globalDocument = _dereq_('global/document'); -/** - * Add a CSS class name to the component's element - * - * @param {String} classToAdd Classname to add - * @return {vjs.Component} - */ -vjs.Component.prototype.addClass = function(classToAdd){ - vjs.addClass(this.el_, classToAdd); - return this; -}; +var _globalDocument2 = _interopRequireDefault(_globalDocument); -/** - * Remove a CSS class name from the component's element - * - * @param {String} classToRemove Classname to remove - * @return {vjs.Component} - */ -vjs.Component.prototype.removeClass = function(classToRemove){ - vjs.removeClass(this.el_, classToRemove); - return this; -}; +var _objectAssign = _dereq_('object.assign'); -/** - * Show the component element if hidden - * - * @return {vjs.Component} - */ -vjs.Component.prototype.show = function(){ - this.removeClass('vjs-hidden'); - return this; -}; +var _objectAssign2 = _interopRequireDefault(_objectAssign); /** - * Hide the component element if currently showing + * Clickable Component which is clickable or keyboard actionable, but is not a native HTML button * - * @return {vjs.Component} + * @param {Object} player Main Player + * @param {Object=} options Object of option names and values + * @extends Component + * @class ClickableComponent */ -vjs.Component.prototype.hide = function(){ - this.addClass('vjs-hidden'); - return this; -}; -/** - * Lock an item in its visible state - * To be used with fadeIn/fadeOut. - * - * @return {vjs.Component} - * @private - */ -vjs.Component.prototype.lockShowing = function(){ - this.addClass('vjs-lock-showing'); - return this; -}; +var ClickableComponent = (function (_Component) { + _inherits(ClickableComponent, _Component); -/** - * Unlock an item to be hidden - * To be used with fadeIn/fadeOut. - * - * @return {vjs.Component} - * @private - */ -vjs.Component.prototype.unlockShowing = function(){ - this.removeClass('vjs-lock-showing'); - return this; -}; + function ClickableComponent(player, options) { + _classCallCheck(this, ClickableComponent); -/** - * Disable component by making it unshowable - * - * Currently private because we're moving towards more css-based states. - * @private - */ -vjs.Component.prototype.disable = function(){ - this.hide(); - this.show = function(){}; -}; + _Component.call(this, player, options); -/** - * Set or get the width of the component (CSS values) - * - * Setting the video tag dimension values only works with values in pixels. - * Percent values will not work. - * Some percents can be used, but width()/height() will return the number + %, - * not the actual computed width/height. - * - * @param {Number|String=} num Optional width number - * @param {Boolean} skipListeners Skip the 'resize' event trigger - * @return {vjs.Component} This component, when setting the width - * @return {Number|String} The width, when getting - */ -vjs.Component.prototype.width = function(num, skipListeners){ - return this.dimension('width', num, skipListeners); -}; + this.emitTapEvents(); -/** - * Get or set the height of the component (CSS values) - * - * Setting the video tag dimension values only works with values in pixels. - * Percent values will not work. - * Some percents can be used, but width()/height() will return the number + %, - * not the actual computed width/height. - * - * @param {Number|String=} num New component height - * @param {Boolean=} skipListeners Skip the resize event trigger - * @return {vjs.Component} This component, when setting the height - * @return {Number|String} The height, when getting - */ -vjs.Component.prototype.height = function(num, skipListeners){ - return this.dimension('height', num, skipListeners); -}; + this.on('tap', this.handleClick); + this.on('click', this.handleClick); + this.on('focus', this.handleFocus); + this.on('blur', this.handleBlur); + } -/** - * Set both width and height at the same time - * - * @param {Number|String} width - * @param {Number|String} height - * @return {vjs.Component} The component - */ -vjs.Component.prototype.dimensions = function(width, height){ - // Skip resize listeners on width for optimization - return this.width(width, true).height(height); -}; + /** + * Create the component's DOM element + * + * @param {String=} type Element's node type. e.g. 'div' + * @param {Object=} props An object of properties that should be set on the element + * @param {Object=} attributes An object of attributes that should be set on the element + * @return {Element} + * @method createEl + */ -/** - * Get or set width or height - * - * This is the shared code for the width() and height() methods. - * All for an integer, integer + 'px' or integer + '%'; - * - * Known issue: Hidden elements officially have a width of 0. We're defaulting - * to the style.width value and falling back to computedStyle which has the - * hidden element issue. Info, but probably not an efficient fix: - * http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/ - * - * @param {String} widthOrHeight 'width' or 'height' - * @param {Number|String=} num New dimension - * @param {Boolean=} skipListeners Skip resize event trigger - * @return {vjs.Component} The component if a dimension was set - * @return {Number|String} The dimension if nothing was set - * @private - */ -vjs.Component.prototype.dimension = function(widthOrHeight, num, skipListeners){ - if (num !== undefined) { - if (num === null || vjs.isNaN(num)) { - num = 0; - } + ClickableComponent.prototype.createEl = function createEl() { + var tag = arguments.length <= 0 || arguments[0] === undefined ? 'div' : arguments[0]; + var props = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + var attributes = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; - // Check if using css width/height (% or px) and adjust - if ((''+num).indexOf('%') !== -1 || (''+num).indexOf('px') !== -1) { - this.el_.style[widthOrHeight] = num; - } else if (num === 'auto') { - this.el_.style[widthOrHeight] = ''; - } else { - this.el_.style[widthOrHeight] = num+'px'; - } + props = _objectAssign2['default']({ + className: this.buildCSSClass(), + tabIndex: 0 + }, props); - // skipListeners allows us to avoid triggering the resize event when setting both width and height - if (!skipListeners) { this.trigger('resize'); } + if (tag === 'button') { + _utilsLogJs2['default'].error('Creating a ClickableComponent with an HTML element of ' + tag + ' is not supported; use a Button instead.'); + } - // Return component - return this; - } + // Add ARIA attributes for clickable element which is not a native HTML button + attributes = _objectAssign2['default']({ + role: 'button', + 'aria-live': 'polite' // let the screen reader user know that the text of the element may change + }, attributes); - // Not setting a value, so getting it - // Make sure element exists - if (!this.el_) return 0; + var el = _Component.prototype.createEl.call(this, tag, props, attributes); - // Get dimension value from style - var val = this.el_.style[widthOrHeight]; - var pxIndex = val.indexOf('px'); - if (pxIndex !== -1) { - // Return the pixel value with no 'px' - return parseInt(val.slice(0,pxIndex), 10); + this.createControlTextEl(el); - // No px so using % or no style was set, so falling back to offsetWidth/height - // If component has display:none, offset will return 0 - // TODO: handle display:none and no dimension style using px - } else { + return el; + }; - return parseInt(this.el_['offset'+vjs.capitalize(widthOrHeight)], 10); + /** + * create control text + * + * @param {Element} el Parent element for the control text + * @return {Element} + * @method controlText + */ - // ComputedStyle version. - // Only difference is if the element is hidden it will return - // the percent value (e.g. '100%'') - // instead of zero like offsetWidth returns. - // var val = vjs.getComputedStyleValue(this.el_, widthOrHeight); - // var pxIndex = val.indexOf('px'); + ClickableComponent.prototype.createControlTextEl = function createControlTextEl(el) { + this.controlTextEl_ = Dom.createEl('span', { + className: 'vjs-control-text' + }); - // if (pxIndex !== -1) { - // return val.slice(0, pxIndex); - // } else { - // return val; - // } - } -}; + if (el) { + el.appendChild(this.controlTextEl_); + } -/** - * Fired when the width and/or height of the component changes - * @event resize - */ -vjs.Component.prototype.onResize; + this.controlText(this.controlText_, el); -/** - * Emit 'tap' events when touch events are supported - * - * This is used to support toggling the controls through a tap on the video. - * - * We're requiring them to be enabled because otherwise every component would - * have this extra overhead unnecessarily, on mobile devices where extra - * overhead is especially bad. - * @private - */ -vjs.Component.prototype.emitTapEvents = function(){ - var touchStart, firstTouch, touchTime, couldBeTap, noTap, - xdiff, ydiff, touchDistance, tapMovementThreshold, touchTimeThreshold; + return this.controlTextEl_; + }; - // Track the start time so we can determine how long the touch lasted - touchStart = 0; - firstTouch = null; + /** + * Controls text - both request and localize + * + * @param {String} text Text for element + * @param {Element=} el Element to set the title on + * @return {String} + * @method controlText + */ - // Maximum movement allowed during a touch event to still be considered a tap - // Other popular libs use anywhere from 2 (hammer.js) to 15, so 10 seems like a nice, round number. - tapMovementThreshold = 10; + ClickableComponent.prototype.controlText = function controlText(text) { + var el = arguments.length <= 1 || arguments[1] === undefined ? this.el() : arguments[1]; - // The maximum length a touch can be while still being considered a tap - touchTimeThreshold = 200; + if (!text) return this.controlText_ || 'Need Text'; - this.on('touchstart', function(event) { - // If more than one finger, don't consider treating this as a click - if (event.touches.length === 1) { - firstTouch = vjs.obj.copy(event.touches[0]); - // Record start time so we can detect a tap vs. "touch and hold" - touchStart = new Date().getTime(); - // Reset couldBeTap tracking - couldBeTap = true; - } - }); + var localizedText = this.localize(text); - this.on('touchmove', function(event) { - // If more than one finger, don't consider treating this as a click - if (event.touches.length > 1) { - couldBeTap = false; - } else if (firstTouch) { - // Some devices will throw touchmoves for all but the slightest of taps. - // So, if we moved only a small distance, this could still be a tap - xdiff = event.touches[0].pageX - firstTouch.pageX; - ydiff = event.touches[0].pageY - firstTouch.pageY; - touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff); - if (touchDistance > tapMovementThreshold) { - couldBeTap = false; - } - } - }); + this.controlText_ = text; + this.controlTextEl_.innerHTML = localizedText; + el.setAttribute('title', localizedText); - noTap = function(){ - couldBeTap = false; - }; - // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s - this.on('touchleave', noTap); - this.on('touchcancel', noTap); - - // When the touch ends, measure how long it took and trigger the appropriate - // event - this.on('touchend', function(event) { - firstTouch = null; - // Proceed only if the touchmove/leave/cancel event didn't happen - if (couldBeTap === true) { - // Measure how long the touch lasted - touchTime = new Date().getTime() - touchStart; - // Make sure the touch was less than the threshold to be considered a tap - if (touchTime < touchTimeThreshold) { - event.preventDefault(); // Don't let browser turn this into a click - this.trigger('tap'); - // It may be good to copy the touchend event object and change the - // type to tap, if the other event properties aren't exact after - // vjs.fixEvent runs (e.g. event.target) - } - } - }); -}; + return this; + }; -/** - * Report user touch activity when touch events occur - * - * User activity is used to determine when controls should show/hide. It's - * relatively simple when it comes to mouse events, because any mouse event - * should show the controls. So we capture mouse events that bubble up to the - * player and report activity when that happens. - * - * With touch events it isn't as easy. We can't rely on touch events at the - * player level, because a tap (touchstart + touchend) on the video itself on - * mobile devices is meant to turn controls off (and on). User activity is - * checked asynchronously, so what could happen is a tap event on the video - * turns the controls off, then the touchend event bubbles up to the player, - * which if it reported user activity, would turn the controls right back on. - * (We also don't want to completely block touch events from bubbling up) - * - * Also a touchmove, touch+hold, and anything other than a tap is not supposed - * to turn the controls back on on a mobile device. - * - * Here we're setting the default component behavior to report user activity - * whenever touch events happen, and this can be turned off by components that - * want touch events to act differently. - */ -vjs.Component.prototype.enableTouchActivity = function() { - var report, touchHolding, touchEnd; + /** + * Allows sub components to stack CSS class names + * + * @return {String} + * @method buildCSSClass + */ - // Don't continue if the root player doesn't support reporting user activity - if (!this.player().reportUserActivity) { - return; - } + ClickableComponent.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-control vjs-button ' + _Component.prototype.buildCSSClass.call(this); + }; - // listener for reporting that the user is active - report = vjs.bind(this.player(), this.player().reportUserActivity); + /** + * Adds a child component inside this clickable-component + * + * @param {String|Component} child The class name or instance of a child to add + * @param {Object=} options Options, including options to be passed to children of the child. + * @return {Component} The child component (created by this process if a string was used) + * @method addChild + */ - this.on('touchstart', function() { - report(); - // For as long as the they are touching the device or have their mouse down, - // we consider them active even if they're not moving their finger or mouse. - // So we want to continue to update that they are active - this.clearInterval(touchHolding); - // report at the same interval as activityCheck - touchHolding = this.setInterval(report, 250); - }); + ClickableComponent.prototype.addChild = function addChild(child) { + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; - touchEnd = function(event) { - report(); - // stop the interval that maintains activity if the touch is holding - this.clearInterval(touchHolding); - }; + // TODO: Fix adding an actionable child to a ClickableComponent; currently + // it will cause issues with assistive technology (e.g. screen readers) + // which support ARIA, since an element with role="button" cannot have + // actionable child elements. - this.on('touchmove', report); - this.on('touchend', touchEnd); - this.on('touchcancel', touchEnd); -}; + //let className = this.constructor.name; + //log.warn(`Adding a child to a ClickableComponent (${className}) can cause issues with assistive technology which supports ARIA, since an element with role="button" cannot have actionable child elements.`); -/** - * Creates timeout and sets up disposal automatically. - * @param {Function} fn The function to run after the timeout. - * @param {Number} timeout Number of ms to delay before executing specified function. - * @return {Number} Returns the timeout ID - */ -vjs.Component.prototype.setTimeout = function(fn, timeout) { - fn = vjs.bind(this, fn); + return _Component.prototype.addChild.call(this, child, options); + }; - // window.setTimeout would be preferable here, but due to some bizarre issue with Sinon and/or Phantomjs, we can't. - var timeoutId = setTimeout(fn, timeout); + /** + * Enable the component element + * + * @return {Component} + * @method enable + */ - var disposeFn = function() { - this.clearTimeout(timeoutId); + ClickableComponent.prototype.enable = function enable() { + this.removeClass('vjs-disabled'); + this.el_.setAttribute('aria-disabled', 'false'); + return this; }; - disposeFn.guid = 'vjs-timeout-'+ timeoutId; - - this.on('dispose', disposeFn); + /** + * Disable the component element + * + * @return {Component} + * @method disable + */ - return timeoutId; -}; + ClickableComponent.prototype.disable = function disable() { + this.addClass('vjs-disabled'); + this.el_.setAttribute('aria-disabled', 'true'); + return this; + }; + /** + * Handle Click - Override with specific functionality for component + * + * @method handleClick + */ -/** - * Clears a timeout and removes the associated dispose listener - * @param {Number} timeoutId The id of the timeout to clear - * @return {Number} Returns the timeout ID - */ -vjs.Component.prototype.clearTimeout = function(timeoutId) { - clearTimeout(timeoutId); + ClickableComponent.prototype.handleClick = function handleClick() {}; - var disposeFn = function(){}; - disposeFn.guid = 'vjs-timeout-'+ timeoutId; + /** + * Handle Focus - Add keyboard functionality to element + * + * @method handleFocus + */ - this.off('dispose', disposeFn); + ClickableComponent.prototype.handleFocus = function handleFocus() { + Events.on(_globalDocument2['default'], 'keydown', Fn.bind(this, this.handleKeyPress)); + }; - return timeoutId; -}; + /** + * Handle KeyPress (document level) - Trigger click when Space or Enter key is pressed + * + * @method handleKeyPress + */ -/** - * Creates an interval and sets up disposal automatically. - * @param {Function} fn The function to run every N seconds. - * @param {Number} interval Number of ms to delay before executing specified function. - * @return {Number} Returns the interval ID - */ -vjs.Component.prototype.setInterval = function(fn, interval) { - fn = vjs.bind(this, fn); + ClickableComponent.prototype.handleKeyPress = function handleKeyPress(event) { + // Support Space (32) or Enter (13) key operation to fire a click event + if (event.which === 32 || event.which === 13) { + event.preventDefault(); + this.handleClick(event); + } else if (_Component.prototype.handleKeyPress) { + _Component.prototype.handleKeyPress.call(this, event); // Pass keypress handling up for unsupported keys + } + }; - var intervalId = setInterval(fn, interval); + /** + * Handle Blur - Remove keyboard triggers + * + * @method handleBlur + */ - var disposeFn = function() { - this.clearInterval(intervalId); + ClickableComponent.prototype.handleBlur = function handleBlur() { + Events.off(_globalDocument2['default'], 'keydown', Fn.bind(this, this.handleKeyPress)); }; - disposeFn.guid = 'vjs-interval-'+ intervalId; + return ClickableComponent; +})(_component2['default']); - this.on('dispose', disposeFn); +_component2['default'].registerComponent('ClickableComponent', ClickableComponent); +exports['default'] = ClickableComponent; +module.exports = exports['default']; - return intervalId; -}; +},{"./component":67,"./utils/dom.js":142,"./utils/events.js":143,"./utils/fn.js":144,"./utils/log.js":147,"global/document":7,"object.assign":53}],66:[function(_dereq_,module,exports){ +'use strict'; -/** - * Clears an interval and removes the associated dispose listener - * @param {Number} intervalId The id of the interval to clear - * @return {Number} Returns the interval ID - */ -vjs.Component.prototype.clearInterval = function(intervalId) { - clearInterval(intervalId); +exports.__esModule = true; - var disposeFn = function(){}; - disposeFn.guid = 'vjs-interval-'+ intervalId; +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - this.off('dispose', disposeFn); +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - return intervalId; -}; -/* Button - Base class for all buttons -================================================================================ */ -/** - * Base class for all buttons - * @param {vjs.Player|Object} player - * @param {Object=} options - * @class - * @constructor - */ -vjs.Button = vjs.Component.extend({ - /** - * @constructor - * @inheritDoc - */ - init: function(player, options){ - vjs.Component.call(this, player, options); +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - this.emitTapEvents(); +var _button = _dereq_('./button'); - this.on('tap', this.onClick); - this.on('click', this.onClick); - this.on('focus', this.onFocus); - this.on('blur', this.onBlur); - } -}); +var _button2 = _interopRequireDefault(_button); -vjs.Button.prototype.createEl = function(type, props){ - var el; +var _component = _dereq_('./component'); - // Add standard Aria and Tabindex info - props = vjs.obj.merge({ - className: this.buildCSSClass(), - 'role': 'button', - 'aria-live': 'polite', // let the screen reader user know that the text of the button may change - tabIndex: 0 - }, props); +var _component2 = _interopRequireDefault(_component); - el = vjs.Component.prototype.createEl.call(this, type, props); +/** + * The `CloseButton` component is a button which fires a "close" event + * when it is activated. + * + * @extends Button + * @class CloseButton + */ - // if innerHTML hasn't been overridden (bigPlayButton), add content elements - if (!props.innerHTML) { - this.contentEl_ = vjs.createEl('div', { - className: 'vjs-control-content' - }); +var CloseButton = (function (_Button) { + _inherits(CloseButton, _Button); - this.controlText_ = vjs.createEl('span', { - className: 'vjs-control-text', - innerHTML: this.localize(this.buttonText) || 'Need Text' - }); + function CloseButton(player, options) { + _classCallCheck(this, CloseButton); - this.contentEl_.appendChild(this.controlText_); - el.appendChild(this.contentEl_); + _Button.call(this, player, options); + this.controlText(options && options.controlText || this.localize('Close')); } - return el; -}; - -vjs.Button.prototype.buildCSSClass = function(){ - // TODO: Change vjs-control to vjs-button? - return 'vjs-control ' + vjs.Component.prototype.buildCSSClass.call(this); -}; + CloseButton.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-close-button ' + _Button.prototype.buildCSSClass.call(this); + }; - // Click - Override with specific functionality for button -vjs.Button.prototype.onClick = function(){}; + CloseButton.prototype.handleClick = function handleClick() { + this.trigger({ type: 'close', bubbles: false }); + }; - // Focus - Add keyboard functionality to element -vjs.Button.prototype.onFocus = function(){ - vjs.on(document, 'keydown', vjs.bind(this, this.onKeyPress)); -}; + return CloseButton; +})(_button2['default']); - // KeyPress (document level) - Trigger click when keys are pressed -vjs.Button.prototype.onKeyPress = function(event){ - // Check for space bar (32) or enter (13) keys - if (event.which == 32 || event.which == 13) { - event.preventDefault(); - this.onClick(); - } -}; +_component2['default'].registerComponent('CloseButton', CloseButton); +exports['default'] = CloseButton; +module.exports = exports['default']; -// Blur - Remove keyboard triggers -vjs.Button.prototype.onBlur = function(){ - vjs.off(document, 'keydown', vjs.bind(this, this.onKeyPress)); -}; -/* Slider -================================================================================ */ +},{"./button":64,"./component":67}],67:[function(_dereq_,module,exports){ /** - * The base functionality for sliders like the volume bar and seek bar + * @file component.js * - * @param {vjs.Player|Object} player - * @param {Object=} options - * @constructor + * Player Component - Base class for all UI objects */ -vjs.Slider = vjs.Component.extend({ - /** @constructor */ - init: function(player, options){ - vjs.Component.call(this, player, options); - // Set property names to bar and handle to match with the child Slider class is looking for - this.bar = this.getChild(this.options_['barName']); - this.handle = this.getChild(this.options_['handleName']); +'use strict'; - this.on('mousedown', this.onMouseDown); - this.on('touchstart', this.onMouseDown); - this.on('focus', this.onFocus); - this.on('blur', this.onBlur); - this.on('click', this.onClick); +exports.__esModule = true; - this.on(player, 'controlsvisible', this.update); - this.on(player, this.playerEvent, this.update); - } -}); +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } -vjs.Slider.prototype.createEl = function(type, props) { - props = props || {}; - // Add the slider element class to all sub classes - props.className = props.className + ' vjs-slider'; - props = vjs.obj.merge({ - 'role': 'slider', - 'aria-valuenow': 0, - 'aria-valuemin': 0, - 'aria-valuemax': 100, - tabIndex: 0 - }, props); - - return vjs.Component.prototype.createEl.call(this, type, props); -}; +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } -vjs.Slider.prototype.onMouseDown = function(event){ - event.preventDefault(); - vjs.blockTextSelection(); - this.addClass('vjs-sliding'); +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - this.on(document, 'mousemove', this.onMouseMove); - this.on(document, 'mouseup', this.onMouseUp); - this.on(document, 'touchmove', this.onMouseMove); - this.on(document, 'touchend', this.onMouseUp); +var _globalWindow = _dereq_('global/window'); - this.onMouseMove(event); -}; +var _globalWindow2 = _interopRequireDefault(_globalWindow); -// To be overridden by a subclass -vjs.Slider.prototype.onMouseMove = function(){}; +var _utilsDomJs = _dereq_('./utils/dom.js'); -vjs.Slider.prototype.onMouseUp = function() { - vjs.unblockTextSelection(); - this.removeClass('vjs-sliding'); +var Dom = _interopRequireWildcard(_utilsDomJs); - this.off(document, 'mousemove', this.onMouseMove); - this.off(document, 'mouseup', this.onMouseUp); - this.off(document, 'touchmove', this.onMouseMove); - this.off(document, 'touchend', this.onMouseUp); +var _utilsFnJs = _dereq_('./utils/fn.js'); - this.update(); -}; +var Fn = _interopRequireWildcard(_utilsFnJs); -vjs.Slider.prototype.update = function(){ - // In VolumeBar init we have a setTimeout for update that pops and update to the end of the - // execution stack. The player is destroyed before then update will cause an error - if (!this.el_) return; +var _utilsGuidJs = _dereq_('./utils/guid.js'); - // If scrubbing, we could use a cached value to make the handle keep up with the user's mouse. - // On HTML5 browsers scrubbing is really smooth, but some flash players are slow, so we might want to utilize this later. - // var progress = (this.player_.scrubbing) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration(); +var Guid = _interopRequireWildcard(_utilsGuidJs); - var barProgress, - progress = this.getPercent(), - handle = this.handle, - bar = this.bar; +var _utilsEventsJs = _dereq_('./utils/events.js'); - // Protect against no duration and other division issues - if (typeof progress !== 'number' || - progress !== progress || - progress < 0 || - progress === Infinity) { - progress = 0; - } +var Events = _interopRequireWildcard(_utilsEventsJs); - barProgress = progress; +var _utilsLogJs = _dereq_('./utils/log.js'); - // If there is a handle, we need to account for the handle in our calculation for progress bar - // so that it doesn't fall short of or extend past the handle. - if (handle) { +var _utilsLogJs2 = _interopRequireDefault(_utilsLogJs); - var box = this.el_, - boxWidth = box.offsetWidth, +var _utilsToTitleCaseJs = _dereq_('./utils/to-title-case.js'); - handleWidth = handle.el().offsetWidth, +var _utilsToTitleCaseJs2 = _interopRequireDefault(_utilsToTitleCaseJs); - // The width of the handle in percent of the containing box - // In IE, widths may not be ready yet causing NaN - handlePercent = (handleWidth) ? handleWidth / boxWidth : 0, +var _objectAssign = _dereq_('object.assign'); - // Get the adjusted size of the box, considering that the handle's center never touches the left or right side. - // There is a margin of half the handle's width on both sides. - boxAdjustedPercent = 1 - handlePercent, +var _objectAssign2 = _interopRequireDefault(_objectAssign); - // Adjust the progress that we'll use to set widths to the new adjusted box width - adjustedProgress = progress * boxAdjustedPercent; +var _utilsMergeOptionsJs = _dereq_('./utils/merge-options.js'); - // The bar does reach the left side, so we need to account for this in the bar's width - barProgress = adjustedProgress + (handlePercent / 2); +var _utilsMergeOptionsJs2 = _interopRequireDefault(_utilsMergeOptionsJs); - // Move the handle from the left based on the adjected progress - handle.el().style.left = vjs.round(adjustedProgress * 100, 2) + '%'; - } +/** + * Base UI Component class + * Components are embeddable UI objects that are represented by both a + * javascript object and an element in the DOM. They can be children of other + * components, and can have many children themselves. + * ```js + * // adding a button to the player + * var button = player.addChild('button'); + * button.el(); // -> button element + * ``` + * ```html + *
+ *
Button
+ *
+ * ``` + * Components are also event targets. + * ```js + * button.on('click', function(){ + * console.log('Button Clicked!'); + * }); + * button.trigger('customevent'); + * ``` + * + * @param {Object} player Main Player + * @param {Object=} options Object of option names and values + * @param {Function=} ready Ready callback function + * @class Component + */ - // Set the new bar width - if (bar) { - bar.el().style.width = vjs.round(barProgress * 100, 2) + '%'; - } -}; +var Component = (function () { + function Component(player, options, ready) { + _classCallCheck(this, Component); -vjs.Slider.prototype.calculateDistance = function(event){ - var el, box, boxX, boxY, boxW, boxH, handle, pageX, pageY; + // The component might be the player itself and we can't pass `this` to super + if (!player && this.play) { + this.player_ = player = this; // eslint-disable-line + } else { + this.player_ = player; + } - el = this.el_; - box = vjs.findPosition(el); - boxW = boxH = el.offsetWidth; - handle = this.handle; + // Make a copy of prototype.options_ to protect against overriding defaults + this.options_ = _utilsMergeOptionsJs2['default']({}, this.options_); - if (this.options()['vertical']) { - boxY = box.top; + // Updated options with supplied options + options = this.options_ = _utilsMergeOptionsJs2['default'](this.options_, options); - if (event.changedTouches) { - pageY = event.changedTouches[0].pageY; - } else { - pageY = event.pageY; - } + // Get ID from options or options element if one is supplied + this.id_ = options.id || options.el && options.el.id; - if (handle) { - var handleH = handle.el().offsetHeight; - // Adjusted X and Width, so handle doesn't go outside the bar - boxY = boxY + (handleH / 2); - boxH = boxH - handleH; - } + // If there was no ID from the options, generate one + if (!this.id_) { + // Don't require the player ID function in the case of mock players + var id = player && player.id && player.id() || 'no_player'; - // Percent that the click is through the adjusted area - return Math.max(0, Math.min(1, ((boxY - pageY) + boxH) / boxH)); + this.id_ = id + '_component_' + Guid.newGUID(); + } - } else { - boxX = box.left; + this.name_ = options.name || null; - if (event.changedTouches) { - pageX = event.changedTouches[0].pageX; - } else { - pageX = event.pageX; + // Create element if one wasn't provided in options + if (options.el) { + this.el_ = options.el; + } else if (options.createEl !== false) { + this.el_ = this.createEl(); } - if (handle) { - var handleW = handle.el().offsetWidth; + this.children_ = []; + this.childIndex_ = {}; + this.childNameIndex_ = {}; - // Adjusted X and Width, so handle doesn't go outside the bar - boxX = boxX + (handleW / 2); - boxW = boxW - handleW; + // Add any child components in options + if (options.initChildren !== false) { + this.initChildren(); } - // Percent that the click is through the adjusted area - return Math.max(0, Math.min(1, (pageX - boxX) / boxW)); - } -}; - -vjs.Slider.prototype.onFocus = function(){ - this.on(document, 'keydown', this.onKeyPress); -}; + this.ready(ready); + // Don't want to trigger ready here or it will before init is actually + // finished for all children that run this constructor -vjs.Slider.prototype.onKeyPress = function(event){ - if (event.which == 37 || event.which == 40) { // Left and Down Arrows - event.preventDefault(); - this.stepBack(); - } else if (event.which == 38 || event.which == 39) { // Up and Right Arrows - event.preventDefault(); - this.stepForward(); + if (options.reportTouchActivity !== false) { + this.enableTouchActivity(); + } } -}; -vjs.Slider.prototype.onBlur = function(){ - this.off(document, 'keydown', this.onKeyPress); -}; + /** + * Dispose of the component and all child components + * + * @method dispose + */ -/** - * Listener for click events on slider, used to prevent clicks - * from bubbling up to parent elements like button menus. - * @param {Object} event Event object - */ -vjs.Slider.prototype.onClick = function(event){ - event.stopImmediatePropagation(); - event.preventDefault(); -}; + Component.prototype.dispose = function dispose() { + this.trigger({ type: 'dispose', bubbles: false }); -/** - * SeekBar Behavior includes play progress bar, and seek handle - * Needed so it can determine seek position based on handle position/size - * @param {vjs.Player|Object} player - * @param {Object=} options - * @constructor - */ -vjs.SliderHandle = vjs.Component.extend(); + // Dispose all children. + if (this.children_) { + for (var i = this.children_.length - 1; i >= 0; i--) { + if (this.children_[i].dispose) { + this.children_[i].dispose(); + } + } + } -/** - * Default value of the slider - * - * @type {Number} - * @private - */ -vjs.SliderHandle.prototype.defaultValue = 0; + // Delete child references + this.children_ = null; + this.childIndex_ = null; + this.childNameIndex_ = null; -/** @inheritDoc */ -vjs.SliderHandle.prototype.createEl = function(type, props) { - props = props || {}; - // Add the slider element class to all sub classes - props.className = props.className + ' vjs-slider-handle'; - props = vjs.obj.merge({ - innerHTML: ''+this.defaultValue+'' - }, props); + // Remove all event listeners. + this.off(); - return vjs.Component.prototype.createEl.call(this, 'div', props); -}; -/* Menu -================================================================================ */ -/** - * The Menu component is used to build pop up menus, including subtitle and - * captions selection menus. - * - * @param {vjs.Player|Object} player - * @param {Object=} options - * @class - * @constructor - */ -vjs.Menu = vjs.Component.extend(); - -/** - * Add a menu item to the menu - * @param {Object|String} component Component or component type to add - */ -vjs.Menu.prototype.addItem = function(component){ - this.addChild(component); - component.on('click', vjs.bind(this, function(){ - this.unlockShowing(); - })); -}; - -/** @inheritDoc */ -vjs.Menu.prototype.createEl = function(){ - var contentElType = this.options().contentElType || 'ul'; - this.contentEl_ = vjs.createEl(contentElType, { - className: 'vjs-menu-content' - }); - var el = vjs.Component.prototype.createEl.call(this, 'div', { - append: this.contentEl_, - className: 'vjs-menu' - }); - el.appendChild(this.contentEl_); - - // Prevent clicks from bubbling up. Needed for Menu Buttons, - // where a click on the parent is significant - vjs.on(el, 'click', function(event){ - event.preventDefault(); - event.stopImmediatePropagation(); - }); - - return el; -}; - -/** - * The component for a menu item. `
  • ` - * - * @param {vjs.Player|Object} player - * @param {Object=} options - * @class - * @constructor - */ -vjs.MenuItem = vjs.Button.extend({ - /** @constructor */ - init: function(player, options){ - vjs.Button.call(this, player, options); - this.selected(options['selected']); - } -}); - -/** @inheritDoc */ -vjs.MenuItem.prototype.createEl = function(type, props){ - return vjs.Button.prototype.createEl.call(this, 'li', vjs.obj.merge({ - className: 'vjs-menu-item', - innerHTML: this.localize(this.options_['label']) - }, props)); -}; - -/** - * Handle a click on the menu item, and set it to selected - */ -vjs.MenuItem.prototype.onClick = function(){ - this.selected(true); -}; - -/** - * Set this menu item as selected or not - * @param {Boolean} selected - */ -vjs.MenuItem.prototype.selected = function(selected){ - if (selected) { - this.addClass('vjs-selected'); - this.el_.setAttribute('aria-selected',true); - } else { - this.removeClass('vjs-selected'); - this.el_.setAttribute('aria-selected',false); - } -}; + // Remove element from DOM + if (this.el_.parentNode) { + this.el_.parentNode.removeChild(this.el_); + } + Dom.removeElData(this.el_); + this.el_ = null; + }; -/** - * A button class with a popup menu - * @param {vjs.Player|Object} player - * @param {Object=} options - * @constructor - */ -vjs.MenuButton = vjs.Button.extend({ - /** @constructor */ - init: function(player, options){ - vjs.Button.call(this, player, options); + /** + * Return the component's player + * + * @return {Player} + * @method player + */ - this.update(); + Component.prototype.player = function player() { + return this.player_; + }; - this.on('keydown', this.onKeyPress); - this.el_.setAttribute('aria-haspopup', true); - this.el_.setAttribute('role', 'button'); - } -}); + /** + * Deep merge of options objects + * Whenever a property is an object on both options objects + * the two properties will be merged using mergeOptions. + * + * ```js + * Parent.prototype.options_ = { + * optionSet: { + * 'childOne': { 'foo': 'bar', 'asdf': 'fdsa' }, + * 'childTwo': {}, + * 'childThree': {} + * } + * } + * newOptions = { + * optionSet: { + * 'childOne': { 'foo': 'baz', 'abc': '123' } + * 'childTwo': null, + * 'childFour': {} + * } + * } + * + * this.options(newOptions); + * ``` + * RESULT + * ```js + * { + * optionSet: { + * 'childOne': { 'foo': 'baz', 'asdf': 'fdsa', 'abc': '123' }, + * 'childTwo': null, // Disabled. Won't be initialized. + * 'childThree': {}, + * 'childFour': {} + * } + * } + * ``` + * + * @param {Object} obj Object of new option values + * @return {Object} A NEW object of this.options_ and obj merged + * @method options + */ -vjs.MenuButton.prototype.update = function() { - var menu = this.createMenu(); + Component.prototype.options = function options(obj) { + _utilsLogJs2['default'].warn('this.options() has been deprecated and will be moved to the constructor in 6.0'); - if (this.menu) { - this.removeChild(this.menu); - } + if (!obj) { + return this.options_; + } - this.menu = menu; - this.addChild(menu); + this.options_ = _utilsMergeOptionsJs2['default'](this.options_, obj); + return this.options_; + }; - if (this.items && this.items.length === 0) { - this.hide(); - } else if (this.items && this.items.length > 1) { - this.show(); - } -}; + /** + * Get the component's DOM element + * ```js + * var domEl = myComponent.el(); + * ``` + * + * @return {Element} + * @method el + */ -/** - * Track the state of the menu button - * @type {Boolean} - * @private - */ -vjs.MenuButton.prototype.buttonPressed_ = false; + Component.prototype.el = function el() { + return this.el_; + }; -vjs.MenuButton.prototype.createMenu = function(){ - var menu = new vjs.Menu(this.player_); + /** + * Create the component's DOM element + * + * @param {String=} tagName Element's node type. e.g. 'div' + * @param {Object=} properties An object of properties that should be set + * @param {Object=} attributes An object of attributes that should be set + * @return {Element} + * @method createEl + */ - // Add a title list item to the top - if (this.options().title) { - menu.contentEl().appendChild(vjs.createEl('li', { - className: 'vjs-menu-title', - innerHTML: vjs.capitalize(this.options().title), - tabindex: -1 - })); - } + Component.prototype.createEl = function createEl(tagName, properties, attributes) { + return Dom.createEl(tagName, properties, attributes); + }; - this.items = this['createItems'](); + Component.prototype.localize = function localize(string) { + var code = this.player_.language && this.player_.language(); + var languages = this.player_.languages && this.player_.languages(); - if (this.items) { - // Add menu items to the menu - for (var i = 0; i < this.items.length; i++) { - menu.addItem(this.items[i]); + if (!code || !languages) { + return string; } - } - - return menu; -}; - -/** - * Create the list of menu items. Specific to each subclass. - */ -vjs.MenuButton.prototype.createItems = function(){}; -/** @inheritDoc */ -vjs.MenuButton.prototype.buildCSSClass = function(){ - return this.className + ' vjs-menu-button ' + vjs.Button.prototype.buildCSSClass.call(this); -}; + var language = languages[code]; -// Focus - Add keyboard functionality to element -// This function is not needed anymore. Instead, the keyboard functionality is handled by -// treating the button as triggering a submenu. When the button is pressed, the submenu -// appears. Pressing the button again makes the submenu disappear. -vjs.MenuButton.prototype.onFocus = function(){}; -// Can't turn off list display that we turned on with focus, because list would go away. -vjs.MenuButton.prototype.onBlur = function(){}; - -vjs.MenuButton.prototype.onClick = function(){ - // When you click the button it adds focus, which will show the menu indefinitely. - // So we'll remove focus when the mouse leaves the button. - // Focus is needed for tab navigation. - this.one('mouseout', vjs.bind(this, function(){ - this.menu.unlockShowing(); - this.el_.blur(); - })); - if (this.buttonPressed_){ - this.unpressButton(); - } else { - this.pressButton(); - } -}; + if (language && language[string]) { + return language[string]; + } -vjs.MenuButton.prototype.onKeyPress = function(event){ + var primaryCode = code.split('-')[0]; + var primaryLang = languages[primaryCode]; - // Check for space bar (32) or enter (13) keys - if (event.which == 32 || event.which == 13) { - if (this.buttonPressed_){ - this.unpressButton(); - } else { - this.pressButton(); - } - event.preventDefault(); - // Check for escape (27) key - } else if (event.which == 27){ - if (this.buttonPressed_){ - this.unpressButton(); + if (primaryLang && primaryLang[string]) { + return primaryLang[string]; } - event.preventDefault(); - } -}; -vjs.MenuButton.prototype.pressButton = function(){ - this.buttonPressed_ = true; - this.menu.lockShowing(); - this.el_.setAttribute('aria-pressed', true); - if (this.items && this.items.length > 0) { - this.items[0].el().focus(); // set the focus to the title of the submenu - } -}; + return string; + }; -vjs.MenuButton.prototype.unpressButton = function(){ - this.buttonPressed_ = false; - this.menu.unlockShowing(); - this.el_.setAttribute('aria-pressed', false); -}; -/** - * Custom MediaError to mimic the HTML5 MediaError - * @param {Number} code The media error code - */ -vjs.MediaError = function(code){ - if (typeof code === 'number') { - this.code = code; - } else if (typeof code === 'string') { - // default code is zero, so this is a custom error - this.message = code; - } else if (typeof code === 'object') { // object - vjs.obj.merge(this, code); - } + /** + * Return the component's DOM element where children are inserted. + * Will either be the same as el() or a new element defined in createEl(). + * + * @return {Element} + * @method contentEl + */ - if (!this.message) { - this.message = vjs.MediaError.defaultMessages[this.code] || ''; - } -}; + Component.prototype.contentEl = function contentEl() { + return this.contentEl_ || this.el_; + }; -/** - * The error code that refers two one of the defined - * MediaError types - * @type {Number} - */ -vjs.MediaError.prototype.code = 0; + /** + * Get the component's ID + * ```js + * var id = myComponent.id(); + * ``` + * + * @return {String} + * @method id + */ -/** - * An optional message to be shown with the error. - * Message is not part of the HTML5 video spec - * but allows for more informative custom errors. - * @type {String} - */ -vjs.MediaError.prototype.message = ''; + Component.prototype.id = function id() { + return this.id_; + }; -/** - * An optional status code that can be set by plugins - * to allow even more detail about the error. - * For example the HLS plugin might provide the specific - * HTTP status code that was returned when the error - * occurred, then allowing a custom error overlay - * to display more information. - * @type {[type]} - */ -vjs.MediaError.prototype.status = null; + /** + * Get the component's name. The name is often used to reference the component. + * ```js + * var name = myComponent.name(); + * ``` + * + * @return {String} + * @method name + */ -vjs.MediaError.errorTypes = [ - 'MEDIA_ERR_CUSTOM', // = 0 - 'MEDIA_ERR_ABORTED', // = 1 - 'MEDIA_ERR_NETWORK', // = 2 - 'MEDIA_ERR_DECODE', // = 3 - 'MEDIA_ERR_SRC_NOT_SUPPORTED', // = 4 - 'MEDIA_ERR_ENCRYPTED' // = 5 -]; + Component.prototype.name = function name() { + return this.name_; + }; -vjs.MediaError.defaultMessages = { - 1: 'You aborted the video playback', - 2: 'A network error caused the video download to fail part-way.', - 3: 'The video playback was aborted due to a corruption problem or because the video used features your browser did not support.', - 4: 'The video could not be loaded, either because the server or network failed or because the format is not supported.', - 5: 'The video is encrypted and we do not have the keys to decrypt it.' -}; + /** + * Get an array of all child components + * ```js + * var kids = myComponent.children(); + * ``` + * + * @return {Array} The children + * @method children + */ -// Add types as properties on MediaError -// e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4; -for (var errNum = 0; errNum < vjs.MediaError.errorTypes.length; errNum++) { - vjs.MediaError[vjs.MediaError.errorTypes[errNum]] = errNum; - // values should be accessible on both the class and instance - vjs.MediaError.prototype[vjs.MediaError.errorTypes[errNum]] = errNum; -} -(function(){ - var apiMap, specApi, browserApi, i; + Component.prototype.children = function children() { + return this.children_; + }; /** - * Store the browser-specific methods for the fullscreen API - * @type {Object|undefined} - * @private + * Returns a child component with the provided ID + * + * @return {Component} + * @method getChildById */ - vjs.browser.fullscreenAPI; - - // browser API methods - // map approach from Screenful.js - https://github.com/sindresorhus/screenfull.js - apiMap = [ - // Spec: https://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html - [ - 'requestFullscreen', - 'exitFullscreen', - 'fullscreenElement', - 'fullscreenEnabled', - 'fullscreenchange', - 'fullscreenerror' - ], - // WebKit - [ - 'webkitRequestFullscreen', - 'webkitExitFullscreen', - 'webkitFullscreenElement', - 'webkitFullscreenEnabled', - 'webkitfullscreenchange', - 'webkitfullscreenerror' - ], - // Old WebKit (Safari 5.1) - [ - 'webkitRequestFullScreen', - 'webkitCancelFullScreen', - 'webkitCurrentFullScreenElement', - 'webkitCancelFullScreen', - 'webkitfullscreenchange', - 'webkitfullscreenerror' - ], - // Mozilla - [ - 'mozRequestFullScreen', - 'mozCancelFullScreen', - 'mozFullScreenElement', - 'mozFullScreenEnabled', - 'mozfullscreenchange', - 'mozfullscreenerror' - ], - // Microsoft - [ - 'msRequestFullscreen', - 'msExitFullscreen', - 'msFullscreenElement', - 'msFullscreenEnabled', - 'MSFullscreenChange', - 'MSFullscreenError' - ] - ]; - - specApi = apiMap[0]; - - // determine the supported set of functions - for (i=0; i - * - * - * ``` - * - * After an instance has been created it can be accessed globally using `Video('example_video_1')`. - * - * @class - * @extends vjs.Component - */ -vjs.Player = vjs.Component.extend({ + Component.prototype.getChild = function getChild(name) { + return this.childNameIndex_[name]; + }; /** - * player's constructor function + * Adds a child component inside this component + * ```js + * myComponent.el(); + * // ->
    + * myComponent.children(); + * // [empty array] * - * @constructs - * @method init - * @param {Element} tag The original video tag used for configuring options - * @param {Object=} options Player options - * @param {Function=} ready Ready callback function + * var myButton = myComponent.addChild('MyButton'); + * // ->
    myButton
    + * // -> myButton === myComponent.children()[0]; + * ``` + * Pass in options for child constructors and options for children of the child + * ```js + * var myButton = myComponent.addChild('MyButton', { + * text: 'Press Me', + * buttonChildExample: { + * buttonChildOption: true + * } + * }); + * ``` + * + * @param {String|Component} child The class name or instance of a child to add + * @param {Object=} options Options, including options to be passed to children of the child. + * @param {Number} index into our children array to attempt to add the child + * @return {Component} The child component (created by this process if a string was used) + * @method addChild */ - init: function(tag, options, ready){ - this.tag = tag; // Store the original tag used to set options - // Make sure tag ID exists - tag.id = tag.id || 'vjs_video_' + vjs.guid++; + Component.prototype.addChild = function addChild(child) { + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + var index = arguments.length <= 2 || arguments[2] === undefined ? this.children_.length : arguments[2]; - // Store the tag attributes used to restore html5 element - this.tagAttributes = tag && vjs.getElementAttributes(tag); + var component = undefined; + var componentName = undefined; - // Set Options - // The options argument overrides options set in the video tag - // which overrides globally set options. - // This latter part coincides with the load order - // (tag must exist before Player) - options = vjs.obj.merge(this.getTagSettings(tag), options); + // If child is a string, create nt with options + if (typeof child === 'string') { + componentName = child; - // Update Current Language - this.language_ = options['language'] || vjs.options['language']; + // Options can also be specified as a boolean, so convert to an empty object if false. + if (!options) { + options = {}; + } - // Update Supported Languages - this.languages_ = options['languages'] || vjs.options['languages']; + // Same as above, but true is deprecated so show a warning. + if (options === true) { + _utilsLogJs2['default'].warn('Initializing a child component with `true` is deprecated. Children should be defined in an array when possible, but if necessary use an object instead of `true`.'); + options = {}; + } - // Cache for video property values. - this.cache_ = {}; + // If no componentClass in options, assume componentClass is the name lowercased + // (e.g. playButton) + var componentClassName = options.componentClass || _utilsToTitleCaseJs2['default'](componentName); - // Set poster - this.poster_ = options['poster'] || ''; + // Set name through options + options.name = componentName; - // Set controls - this.controls_ = !!options['controls']; - // Original tag settings stored in options - // now remove immediately so native controls don't flash. - // May be turned back on by HTML5 tech if nativeControlsForTouch is true - tag.controls = false; + // Create a new object & element for this controls set + // If there's no .player_, this is a player + var ComponentClass = Component.getComponent(componentClassName); - // we don't want the player to report touch activity on itself - // see enableTouchActivity in Component - options.reportTouchActivity = false; + if (!ComponentClass) { + throw new Error('Component ' + componentClassName + ' does not exist'); + } - // Set isAudio based on whether or not an audio tag was used - this.isAudio(this.tag.nodeName.toLowerCase() === 'audio'); + // data stored directly on the videojs object may be + // misidentified as a component to retain + // backwards-compatibility with 4.x. check to make sure the + // component class can be instantiated. + if (typeof ComponentClass !== 'function') { + return null; + } - // Run base component initializing with new options. - // Builds the element through createEl() - // Inits and embeds any child components in opts - vjs.Component.call(this, this, options, ready); + component = new ComponentClass(this.player_ || this, options); - // Update controls className. Can't do this when the controls are initially - // set because the element doesn't exist yet. - if (this.controls()) { - this.addClass('vjs-controls-enabled'); + // child is a component instance } else { - this.addClass('vjs-controls-disabled'); - } + component = child; + } - if (this.isAudio()) { - this.addClass('vjs-audio'); - } + this.children_.splice(index, 0, component); - // TODO: Make this smarter. Toggle user state between touching/mousing - // using events, since devices can have both touch and mouse events. - // if (vjs.TOUCH_ENABLED) { - // this.addClass('vjs-touch-enabled'); - // } + if (typeof component.id === 'function') { + this.childIndex_[component.id()] = component; + } - // Make player easily findable by ID - vjs.players[this.id_] = this; + // If a name wasn't used to create the component, check if we can use the + // name function of the component + componentName = componentName || component.name && component.name(); - if (options['plugins']) { - vjs.obj.each(options['plugins'], function(key, val){ - this[key](val); - }, this); + if (componentName) { + this.childNameIndex_[componentName] = component; } - this.listenForUserActivity(); - } -}); + // Add the UI object's element to the container div (box) + // Having an element is not required + if (typeof component.el === 'function' && component.el()) { + var childNodes = this.contentEl().children; + var refNode = childNodes[index] || null; + this.contentEl().insertBefore(component.el(), refNode); + } -/** - * The player's stored language code - * - * @type {String} - * @private - */ -vjs.Player.prototype.language_; + // Return so it can stored on parent object if desired. + return component; + }; -/** - * The player's language code - * @param {String} languageCode The locale string - * @return {String} The locale string when getting - * @return {vjs.Player} self, when setting - */ -vjs.Player.prototype.language = function (languageCode) { - if (languageCode === undefined) { - return this.language_; - } + /** + * Remove a child component from this component's list of children, and the + * child component's element from this component's element + * + * @param {Component} component Component to remove + * @method removeChild + */ - this.language_ = languageCode; - return this; -}; + Component.prototype.removeChild = function removeChild(component) { + if (typeof component === 'string') { + component = this.getChild(component); + } -/** - * The player's stored language dictionary - * - * @type {Object} - * @private - */ -vjs.Player.prototype.languages_; + if (!component || !this.children_) { + return; + } -vjs.Player.prototype.languages = function(){ - return this.languages_; -}; + var childFound = false; -/** - * Player instance options, surfaced using vjs.options - * vjs.options = vjs.Player.prototype.options_ - * Make changes in vjs.options, not here. - * All options should use string keys so they avoid - * renaming by closure compiler - * @type {Object} - * @private - */ -vjs.Player.prototype.options_ = vjs.options; + for (var i = this.children_.length - 1; i >= 0; i--) { + if (this.children_[i] === component) { + childFound = true; + this.children_.splice(i, 1); + break; + } + } -/** - * Destroys the video player and does any necessary cleanup - * - * myPlayer.dispose(); - * - * This is especially helpful if you are dynamically adding and removing videos - * to/from the DOM. - */ -vjs.Player.prototype.dispose = function(){ - this.trigger('dispose'); - // prevent dispose from being called twice - this.off('dispose'); + if (!childFound) { + return; + } - // Kill reference to this player - vjs.players[this.id_] = null; - if (this.tag && this.tag['player']) { this.tag['player'] = null; } - if (this.el_ && this.el_['player']) { this.el_['player'] = null; } + this.childIndex_[component.id()] = null; + this.childNameIndex_[component.name()] = null; - if (this.tech) { this.tech.dispose(); } + var compEl = component.el(); - // Component dispose - vjs.Component.prototype.dispose.call(this); -}; + if (compEl && compEl.parentNode === this.contentEl()) { + this.contentEl().removeChild(component.el()); + } + }; -vjs.Player.prototype.getTagSettings = function(tag){ - var tagOptions, - dataSetup, - options = { - 'sources': [], - 'tracks': [] - }; + /** + * Add and initialize default child components from options + * ```js + * // when an instance of MyComponent is created, all children in options + * // will be added to the instance by their name strings and options + * MyComponent.prototype.options_ = { + * children: [ + * 'myChildComponent' + * ], + * myChildComponent: { + * myChildOption: true + * } + * }; + * + * // Or when creating the component + * var myComp = new MyComponent(player, { + * children: [ + * 'myChildComponent' + * ], + * myChildComponent: { + * myChildOption: true + * } + * }); + * ``` + * The children option can also be an array of + * child options objects (that also include a 'name' key). + * This can be used if you have two child components of the + * same type that need different options. + * ```js + * var myComp = new MyComponent(player, { + * children: [ + * 'button', + * { + * name: 'button', + * someOtherOption: true + * }, + * { + * name: 'button', + * someOtherOption: false + * } + * ] + * }); + * ``` + * + * @method initChildren + */ - tagOptions = vjs.getElementAttributes(tag); - dataSetup = tagOptions['data-setup']; + Component.prototype.initChildren = function initChildren() { + var _this = this; - // Check if data-setup attr exists. - if (dataSetup !== null){ - // Parse options JSON - // If empty string, make it a parsable json object. - vjs.obj.merge(tagOptions, vjs.JSON.parse(dataSetup || '{}')); - } + var children = this.options_.children; - vjs.obj.merge(options, tagOptions); + if (children) { + (function () { + // `this` is `parent` + var parentOptions = _this.options_; - // Get tag children settings - if (tag.hasChildNodes()) { - var children, child, childName, i, j; + var handleAdd = function handleAdd(child) { + var name = child.name; + var opts = child.opts; - children = tag.childNodes; + // Allow options for children to be set at the parent options + // e.g. videojs(id, { controlBar: false }); + // instead of videojs(id, { children: { controlBar: false }); + if (parentOptions[name] !== undefined) { + opts = parentOptions[name]; + } - for (i=0,j=children.length; i 0) { - techOptions['startTime'] = this.cache_.currentTime; + Component.prototype.off = function off(first, second, third) { + if (!first || typeof first === 'string' || Array.isArray(first)) { + Events.off(this.el_, first, second); + } else { + var target = first; + var type = second; + // Ensure there's at least a guid, even if the function hasn't been used + var fn = Fn.bind(this, third); + + // Remove the dispose listener on this component, + // which was given the same guid as the event listener + this.off('dispose', fn); + + if (first.nodeName) { + // Remove the listener + Events.off(target, type, fn); + // Remove the listener for cleaning the dispose listener + Events.off(target, 'dispose', fn); + } else { + target.off(type, fn); + target.off('dispose', fn); + } } - this.cache_.src = source.src; - } + return this; + }; - // Initialize tech instance - this.tech = new window['videojs'][techName](this, techOptions); + /** + * Add an event listener to be triggered only once and then removed + * ```js + * myComponent.one('eventName', myFunc); + * ``` + * Alternatively you can add a listener to another element or component + * that will be triggered only once. + * ```js + * myComponent.one(otherElement, 'eventName', myFunc); + * myComponent.one(otherComponent, 'eventName', myFunc); + * ``` + * + * @param {String|Component} first The event type or other component + * @param {Function|String} second The listener function or event type + * @param {Function=} third The listener function for other component + * @return {Component} + * @method one + */ - this.tech.ready(techReady); -}; + Component.prototype.one = function one(first, second, third) { + var _this3 = this, + _arguments = arguments; -vjs.Player.prototype.unloadTech = function(){ - this.isReady_ = false; + if (typeof first === 'string' || Array.isArray(first)) { + Events.one(this.el_, first, Fn.bind(this, second)); + } else { + (function () { + var target = first; + var type = second; + var fn = Fn.bind(_this3, third); - this.tech.dispose(); + var newFunc = function newFunc() { + _this3.off(target, type, newFunc); + fn.apply(null, _arguments); + }; - this.tech = false; -}; + // Keep the same function ID so we can remove it later + newFunc.guid = fn.guid; -// There's many issues around changing the size of a Flash (or other plugin) object. -// First is a plugin reload issue in Firefox that has been around for 11 years: https://bugzilla.mozilla.org/show_bug.cgi?id=90268 -// Then with the new fullscreen API, Mozilla and webkit browsers will reload the flash object after going to fullscreen. -// To get around this, we're unloading the tech, caching source and currentTime values, and reloading the tech once the plugin is resized. -// reloadTech: function(betweenFn){ -// vjs.log('unloadingTech') -// this.unloadTech(); -// vjs.log('unloadedTech') -// if (betweenFn) { betweenFn.call(); } -// vjs.log('LoadingTech') -// this.loadTech(this.techName, { src: this.cache_.src }) -// vjs.log('loadedTech') -// }, + _this3.on(target, type, newFunc); + })(); + } -// /* Player event handlers (how the player reacts to certain events) -// ================================================================================ */ + return this; + }; -/** - * Fired when the user agent begins looking for media data - * @event loadstart - */ -vjs.Player.prototype.onLoadStart = function() { - // TODO: Update to use `emptied` event instead. See #1277. + /** + * Trigger an event on an element + * ```js + * myComponent.trigger('eventName'); + * myComponent.trigger({'type':'eventName'}); + * myComponent.trigger('eventName', {data: 'some data'}); + * myComponent.trigger({'type':'eventName'}, {data: 'some data'}); + * ``` + * + * @param {Event|Object|String} event A string (the type) or an event object with a type attribute + * @param {Object} [hash] data hash to pass along with the event + * @return {Component} self + * @method trigger + */ - this.removeClass('vjs-ended'); + Component.prototype.trigger = function trigger(event, hash) { + Events.trigger(this.el_, event, hash); + return this; + }; - // reset the error state - this.error(null); + /** + * Bind a listener to the component's ready state. + * Different from event listeners in that if the ready event has already happened + * it will trigger the function immediately. + * + * @param {Function} fn Ready listener + * @param {Boolean} sync Exec the listener synchronously if component is ready + * @return {Component} + * @method ready + */ - // If it's already playing we want to trigger a firstplay event now. - // The firstplay event relies on both the play and loadstart events - // which can happen in any order for a new source - if (!this.paused()) { - this.trigger('firstplay'); - } else { - // reset the hasStarted state - this.hasStarted(false); - } -}; + Component.prototype.ready = function ready(fn) { + var sync = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; -vjs.Player.prototype.hasStarted_ = false; - -vjs.Player.prototype.hasStarted = function(hasStarted){ - if (hasStarted !== undefined) { - // only update if this is a new value - if (this.hasStarted_ !== hasStarted) { - this.hasStarted_ = hasStarted; - if (hasStarted) { - this.addClass('vjs-has-started'); - // trigger the firstplay event if this newly has played - this.trigger('firstplay'); + if (fn) { + if (this.isReady_) { + if (sync) { + fn.call(this); + } else { + // Call the function asynchronously by default for consistency + this.setTimeout(fn, 1); + } } else { - this.removeClass('vjs-has-started'); + this.readyQueue_ = this.readyQueue_ || []; + this.readyQueue_.push(fn); } } return this; - } - return this.hasStarted_; -}; - -/** - * Fired when the player has initial duration and dimension information - * @event loadedmetadata - */ -vjs.Player.prototype.onLoadedMetaData; + }; -/** - * Fired when the player has downloaded data at the current playback position - * @event loadeddata - */ -vjs.Player.prototype.onLoadedData; + /** + * Trigger the ready listeners + * + * @return {Component} + * @method triggerReady + */ -/** - * Fired when the player has finished downloading the source data - * @event loadedalldata - */ -vjs.Player.prototype.onLoadedAllData; + Component.prototype.triggerReady = function triggerReady() { + this.isReady_ = true; -/** - * Fired whenever the media begins or resumes playback - * @event play - */ -vjs.Player.prototype.onPlay = function(){ - this.removeClass('vjs-ended'); - this.removeClass('vjs-paused'); - this.addClass('vjs-playing'); + // Ensure ready is triggerd asynchronously + this.setTimeout(function () { + var readyQueue = this.readyQueue_; - // hide the poster when the user hits play - // https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play - this.hasStarted(true); -}; + // Reset Ready Queue + this.readyQueue_ = []; -/** - * Fired whenever the media begins waiting - * @event waiting - */ -vjs.Player.prototype.onWaiting = function(){ - this.addClass('vjs-waiting'); -}; + if (readyQueue && readyQueue.length > 0) { + readyQueue.forEach(function (fn) { + fn.call(this); + }, this); + } -/** - * A handler for events that signal that waiting has ended - * which is not consistent between browsers. See #1351 - * @private - */ -vjs.Player.prototype.onWaitEnd = function(){ - this.removeClass('vjs-waiting'); -}; + // Allow for using event listeners also + this.trigger('ready'); + }, 1); + }; -/** - * Fired whenever the player is jumping to a new time - * @event seeking - */ -vjs.Player.prototype.onSeeking = function(){ - this.addClass('vjs-seeking'); -}; + /** + * Finds a single DOM element matching `selector` within the component's + * `contentEl` or another custom context. + * + * @method $ + * @param {String} selector + * A valid CSS selector, which will be passed to `querySelector`. + * + * @param {Element|String} [context=document] + * A DOM element within which to query. Can also be a selector + * string in which case the first matching element will be used + * as context. If missing (or no element matches selector), falls + * back to `document`. + * + * @return {Element|null} + */ -/** - * Fired when the player has finished jumping to a new time - * @event seeked - */ -vjs.Player.prototype.onSeeked = function(){ - this.removeClass('vjs-seeking'); -}; + Component.prototype.$ = function $(selector, context) { + return Dom.$(selector, context || this.contentEl()); + }; -/** - * Fired the first time a video is played - * - * Not part of the HLS spec, and we're not sure if this is the best - * implementation yet, so use sparingly. If you don't have a reason to - * prevent playback, use `myPlayer.one('play');` instead. - * - * @event firstplay - */ -vjs.Player.prototype.onFirstPlay = function(){ - //If the first starttime attribute is specified - //then we will start at the given offset in seconds - if(this.options_['starttime']){ - this.currentTime(this.options_['starttime']); - } + /** + * Finds a all DOM elements matching `selector` within the component's + * `contentEl` or another custom context. + * + * @method $$ + * @param {String} selector + * A valid CSS selector, which will be passed to `querySelectorAll`. + * + * @param {Element|String} [context=document] + * A DOM element within which to query. Can also be a selector + * string in which case the first matching element will be used + * as context. If missing (or no element matches selector), falls + * back to `document`. + * + * @return {NodeList} + */ - this.addClass('vjs-has-started'); -}; + Component.prototype.$$ = function $$(selector, context) { + return Dom.$$(selector, context || this.contentEl()); + }; -/** - * Fired whenever the media has been paused - * @event pause - */ -vjs.Player.prototype.onPause = function(){ - this.removeClass('vjs-playing'); - this.addClass('vjs-paused'); -}; + /** + * Check if a component's element has a CSS class name + * + * @param {String} classToCheck Classname to check + * @return {Component} + * @method hasClass + */ -/** - * Fired when the current playback position has changed - * - * During playback this is fired every 15-250 milliseconds, depending on the - * playback technology in use. - * @event timeupdate - */ -vjs.Player.prototype.onTimeUpdate; + Component.prototype.hasClass = function hasClass(classToCheck) { + return Dom.hasElClass(this.el_, classToCheck); + }; -/** - * Fired while the user agent is downloading media data - * @event progress - */ -vjs.Player.prototype.onProgress = function(){ - // Add custom event for when source is finished downloading. - if (this.bufferedPercent() == 1) { - this.trigger('loadedalldata'); - } -}; + /** + * Add a CSS class name to the component's element + * + * @param {String} classToAdd Classname to add + * @return {Component} + * @method addClass + */ -/** - * Fired when the end of the media resource is reached (currentTime == duration) - * @event ended - */ -vjs.Player.prototype.onEnded = function(){ - this.addClass('vjs-ended'); - if (this.options_['loop']) { - this.currentTime(0); - this.play(); - } else if (!this.paused()) { - this.pause(); - } -}; + Component.prototype.addClass = function addClass(classToAdd) { + Dom.addElClass(this.el_, classToAdd); + return this; + }; -/** - * Fired when the duration of the media resource is first known or changed - * @event durationchange - */ -vjs.Player.prototype.onDurationChange = function(){ - // Allows for caching value instead of asking player each time. - // We need to get the techGet response and check for a value so we don't - // accidentally cause the stack to blow up. - var duration = this.techGet('duration'); - if (duration) { - if (duration < 0) { - duration = Infinity; - } - this.duration(duration); - // Determine if the stream is live and propagate styles down to UI. - if (duration === Infinity) { - this.addClass('vjs-live'); - } else { - this.removeClass('vjs-live'); - } - } -}; + /** + * Remove a CSS class name from the component's element + * + * @param {String} classToRemove Classname to remove + * @return {Component} + * @method removeClass + */ -/** - * Fired when the volume changes - * @event volumechange - */ -vjs.Player.prototype.onVolumeChange; + Component.prototype.removeClass = function removeClass(classToRemove) { + Dom.removeElClass(this.el_, classToRemove); + return this; + }; -/** - * Fired when the player switches in or out of fullscreen mode - * @event fullscreenchange - */ -vjs.Player.prototype.onFullscreenChange = function() { - if (this.isFullscreen()) { - this.addClass('vjs-fullscreen'); - } else { - this.removeClass('vjs-fullscreen'); - } -}; + /** + * Add or remove a CSS class name from the component's element + * + * @param {String} classToToggle + * @param {Boolean|Function} [predicate] + * Can be a function that returns a Boolean. If `true`, the class + * will be added; if `false`, the class will be removed. If not + * given, the class will be added if not present and vice versa. + * + * @return {Component} + * @method toggleClass + */ -/** - * Fired when an error occurs - * @event error - */ -vjs.Player.prototype.onError; + Component.prototype.toggleClass = function toggleClass(classToToggle, predicate) { + Dom.toggleElClass(this.el_, classToToggle, predicate); + return this; + }; -// /* Player API -// ================================================================================ */ + /** + * Show the component element if hidden + * + * @return {Component} + * @method show + */ -/** - * Object for cached values. - * @private - */ -vjs.Player.prototype.cache_; + Component.prototype.show = function show() { + this.removeClass('vjs-hidden'); + return this; + }; -vjs.Player.prototype.getCache = function(){ - return this.cache_; -}; + /** + * Hide the component element if currently showing + * + * @return {Component} + * @method hide + */ -// Pass values to the playback tech -vjs.Player.prototype.techCall = function(method, arg){ - // If it's not ready yet, call method when it is - if (this.tech && !this.tech.isReady_) { - this.tech.ready(function(){ - this[method](arg); - }); + Component.prototype.hide = function hide() { + this.addClass('vjs-hidden'); + return this; + }; - // Otherwise call method now - } else { - try { - this.tech[method](arg); - } catch(e) { - vjs.log(e); - throw e; - } - } -}; + /** + * Lock an item in its visible state + * To be used with fadeIn/fadeOut. + * + * @return {Component} + * @private + * @method lockShowing + */ -// Get calls can't wait for the tech, and sometimes don't need to. -vjs.Player.prototype.techGet = function(method){ - if (this.tech && this.tech.isReady_) { + Component.prototype.lockShowing = function lockShowing() { + this.addClass('vjs-lock-showing'); + return this; + }; - // Flash likes to die and reload when you hide or reposition it. - // In these cases the object methods go away and we get errors. - // When that happens we'll catch the errors and inform tech that it's not ready any more. - try { - return this.tech[method](); - } catch(e) { - // When building additional tech libs, an expected method may not be defined yet - if (this.tech[method] === undefined) { - vjs.log('Video.js: ' + method + ' method not defined for '+this.techName+' playback technology.', e); - } else { - // When a method isn't available on the object it throws a TypeError - if (e.name == 'TypeError') { - vjs.log('Video.js: ' + method + ' unavailable on '+this.techName+' playback technology element.', e); - this.tech.isReady_ = false; - } else { - vjs.log(e); - } - } - throw e; - } - } + /** + * Unlock an item to be hidden + * To be used with fadeIn/fadeOut. + * + * @return {Component} + * @private + * @method unlockShowing + */ - return; -}; + Component.prototype.unlockShowing = function unlockShowing() { + this.removeClass('vjs-lock-showing'); + return this; + }; -/** - * start media playback - * - * myPlayer.play(); - * - * @return {vjs.Player} self - */ -vjs.Player.prototype.play = function(){ - this.techCall('play'); - return this; -}; - -/** - * Pause the video playback - * - * myPlayer.pause(); - * - * @return {vjs.Player} self - */ -vjs.Player.prototype.pause = function(){ - this.techCall('pause'); - return this; -}; - -/** - * Check if the player is paused - * - * var isPaused = myPlayer.paused(); - * var isPlaying = !myPlayer.paused(); - * - * @return {Boolean} false if the media is currently playing, or true otherwise - */ -vjs.Player.prototype.paused = function(){ - // The initial state of paused should be true (in Safari it's actually false) - return (this.techGet('paused') === false) ? false : true; -}; - -/** - * Get or set the current time (in seconds) - * - * // get - * var whereYouAt = myPlayer.currentTime(); - * - * // set - * myPlayer.currentTime(120); // 2 minutes into the video - * - * @param {Number|String=} seconds The time to seek to - * @return {Number} The time in seconds, when not setting - * @return {vjs.Player} self, when the current time is set - */ -vjs.Player.prototype.currentTime = function(seconds){ - if (seconds !== undefined) { + /** + * Set or get the width of the component (CSS values) + * Setting the video tag dimension values only works with values in pixels. + * Percent values will not work. + * Some percents can be used, but width()/height() will return the number + %, + * not the actual computed width/height. + * + * @param {Number|String=} num Optional width number + * @param {Boolean} skipListeners Skip the 'resize' event trigger + * @return {Component} This component, when setting the width + * @return {Number|String} The width, when getting + * @method width + */ - this.techCall('setCurrentTime', seconds); + Component.prototype.width = function width(num, skipListeners) { + return this.dimension('width', num, skipListeners); + }; - return this; - } + /** + * Get or set the height of the component (CSS values) + * Setting the video tag dimension values only works with values in pixels. + * Percent values will not work. + * Some percents can be used, but width()/height() will return the number + %, + * not the actual computed width/height. + * + * @param {Number|String=} num New component height + * @param {Boolean=} skipListeners Skip the resize event trigger + * @return {Component} This component, when setting the height + * @return {Number|String} The height, when getting + * @method height + */ - // cache last currentTime and return. default to 0 seconds - // - // Caching the currentTime is meant to prevent a massive amount of reads on the tech's - // currentTime when scrubbing, but may not provide much performance benefit afterall. - // Should be tested. Also something has to read the actual current time or the cache will - // never get updated. - return this.cache_.currentTime = (this.techGet('currentTime') || 0); -}; + Component.prototype.height = function height(num, skipListeners) { + return this.dimension('height', num, skipListeners); + }; -/** - * Get the length in time of the video in seconds - * - * var lengthOfVideo = myPlayer.duration(); - * - * **NOTE**: The video must have started loading before the duration can be - * known, and in the case of Flash, may not be known until the video starts - * playing. - * - * @return {Number} The duration of the video in seconds - */ -vjs.Player.prototype.duration = function(seconds){ - if (seconds !== undefined) { + /** + * Set both width and height at the same time + * + * @param {Number|String} width Width of player + * @param {Number|String} height Height of player + * @return {Component} The component + * @method dimensions + */ - // cache the last set value for optimized scrubbing (esp. Flash) - this.cache_.duration = parseFloat(seconds); + Component.prototype.dimensions = function dimensions(width, height) { + // Skip resize listeners on width for optimization + return this.width(width, true).height(height); + }; - return this; - } + /** + * Get or set width or height + * This is the shared code for the width() and height() methods. + * All for an integer, integer + 'px' or integer + '%'; + * Known issue: Hidden elements officially have a width of 0. We're defaulting + * to the style.width value and falling back to computedStyle which has the + * hidden element issue. Info, but probably not an efficient fix: + * http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/ + * + * @param {String} widthOrHeight 'width' or 'height' + * @param {Number|String=} num New dimension + * @param {Boolean=} skipListeners Skip resize event trigger + * @return {Component} The component if a dimension was set + * @return {Number|String} The dimension if nothing was set + * @private + * @method dimension + */ - if (this.cache_.duration === undefined) { - this.onDurationChange(); - } + Component.prototype.dimension = function dimension(widthOrHeight, num, skipListeners) { + if (num !== undefined) { + // Set to zero if null or literally NaN (NaN !== NaN) + if (num === null || num !== num) { + num = 0; + } - return this.cache_.duration || 0; -}; + // Check if using css width/height (% or px) and adjust + if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) { + this.el_.style[widthOrHeight] = num; + } else if (num === 'auto') { + this.el_.style[widthOrHeight] = ''; + } else { + this.el_.style[widthOrHeight] = num + 'px'; + } -/** - * Calculates how much time is left. - * - * var timeLeft = myPlayer.remainingTime(); - * - * Not a native video element function, but useful - * @return {Number} The time remaining in seconds - */ -vjs.Player.prototype.remainingTime = function(){ - return this.duration() - this.currentTime(); -}; + // skipListeners allows us to avoid triggering the resize event when setting both width and height + if (!skipListeners) { + this.trigger('resize'); + } -// http://dev.w3.org/html5/spec/video.html#dom-media-buffered -// Buffered returns a timerange object. -// Kind of like an array of portions of the video that have been downloaded. + // Return component + return this; + } -/** - * Get a TimeRange object with the times of the video that have been downloaded - * - * If you just want the percent of the video that's been downloaded, - * use bufferedPercent. - * - * // Number of different ranges of time have been buffered. Usually 1. - * numberOfRanges = bufferedTimeRange.length, - * - * // Time in seconds when the first range starts. Usually 0. - * firstRangeStart = bufferedTimeRange.start(0), - * - * // Time in seconds when the first range ends - * firstRangeEnd = bufferedTimeRange.end(0), - * - * // Length in seconds of the first time range - * firstRangeLength = firstRangeEnd - firstRangeStart; - * - * @return {Object} A mock TimeRange object (following HTML spec) - */ -vjs.Player.prototype.buffered = function(){ - var buffered = this.techGet('buffered'); + // Not setting a value, so getting it + // Make sure element exists + if (!this.el_) { + return 0; + } - if (!buffered || !buffered.length) { - buffered = vjs.createTimeRange(0,0); - } + // Get dimension value from style + var val = this.el_.style[widthOrHeight]; + var pxIndex = val.indexOf('px'); - return buffered; -}; + if (pxIndex !== -1) { + // Return the pixel value with no 'px' + return parseInt(val.slice(0, pxIndex), 10); + } -/** - * Get the percent (as a decimal) of the video that's been downloaded - * - * var howMuchIsDownloaded = myPlayer.bufferedPercent(); - * - * 0 means none, 1 means all. - * (This method isn't in the HTML5 spec, but it's very convenient) - * - * @return {Number} A decimal between 0 and 1 representing the percent - */ -vjs.Player.prototype.bufferedPercent = function(){ - var duration = this.duration(), - buffered = this.buffered(), - bufferedDuration = 0, - start, end; + // No px so using % or no style was set, so falling back to offsetWidth/height + // If component has display:none, offset will return 0 + // TODO: handle display:none and no dimension style using px + return parseInt(this.el_['offset' + _utilsToTitleCaseJs2['default'](widthOrHeight)], 10); + }; - if (!duration) { - return 0; - } + /** + * Get width or height of computed style + * @param {String} widthOrHeight 'width' or 'height' + * @return {Number|Boolean} The bolean false if nothing was set + * @method currentDimension + */ - for (var i=0; i duration) { - end = duration; + if (widthOrHeight !== 'width' && widthOrHeight !== 'height') { + throw new Error('currentDimension only accepts width or height value'); } - bufferedDuration += end - start; - } + if (typeof _globalWindow2['default'].getComputedStyle === 'function') { + var computedStyle = _globalWindow2['default'].getComputedStyle(this.el_); + computedWidthOrHeight = computedStyle.getPropertyValue(widthOrHeight) || computedStyle[widthOrHeight]; + } else if (this.el_.currentStyle) { + // ie 8 doesn't support computed style, shim it + // return clientWidth or clientHeight instead for better accuracy + var rule = 'offset' + _utilsToTitleCaseJs2['default'](widthOrHeight); + computedWidthOrHeight = this.el_[rule]; + } - return bufferedDuration / duration; -}; + // remove 'px' from variable and parse as integer + computedWidthOrHeight = parseFloat(computedWidthOrHeight); + return computedWidthOrHeight; + }; -/** - * Get the ending time of the last buffered time range - * - * This is used in the progress bar to encapsulate all time ranges. - * @return {Number} The end of the last buffered time range - */ -vjs.Player.prototype.bufferedEnd = function(){ - var buffered = this.buffered(), - duration = this.duration(), - end = buffered.end(buffered.length-1); + /** + * Get an object which contains width and height values of computed style + * @return {Object} The dimensions of element + * @method currentDimensions + */ - if (end > duration) { - end = duration; - } + Component.prototype.currentDimensions = function currentDimensions() { + return { + width: this.currentDimension('width'), + height: this.currentDimension('height') + }; + }; - return end; -}; + /** + * Get width of computed style + * @return {Integer} + * @method currentWidth + */ -/** - * Get or set the current volume of the media - * - * // get - * var howLoudIsIt = myPlayer.volume(); - * - * // set - * myPlayer.volume(0.5); // Set volume to half - * - * 0 is off (muted), 1.0 is all the way up, 0.5 is half way. - * - * @param {Number} percentAsDecimal The new volume as a decimal percent - * @return {Number} The current volume, when getting - * @return {vjs.Player} self, when setting - */ -vjs.Player.prototype.volume = function(percentAsDecimal){ - var vol; + Component.prototype.currentWidth = function currentWidth() { + return this.currentDimension('width'); + }; - if (percentAsDecimal !== undefined) { - vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); // Force value to between 0 and 1 - this.cache_.volume = vol; - this.techCall('setVolume', vol); - vjs.setLocalStorage('volume', vol); - return this; - } + /** + * Get height of computed style + * @return {Integer} + * @method currentHeight + */ - // Default to 1 when returning current volume. - vol = parseFloat(this.techGet('volume')); - return (isNaN(vol)) ? 1 : vol; -}; + Component.prototype.currentHeight = function currentHeight() { + return this.currentDimension('height'); + }; + /** + * Emit 'tap' events when touch events are supported + * This is used to support toggling the controls through a tap on the video. + * We're requiring them to be enabled because otherwise every component would + * have this extra overhead unnecessarily, on mobile devices where extra + * overhead is especially bad. + * + * @private + * @method emitTapEvents + */ -/** - * Get the current muted state, or turn mute on or off - * - * // get - * var isVolumeMuted = myPlayer.muted(); - * - * // set - * myPlayer.muted(true); // mute the volume - * - * @param {Boolean=} muted True to mute, false to unmute - * @return {Boolean} True if mute is on, false if not, when getting - * @return {vjs.Player} self, when setting mute - */ -vjs.Player.prototype.muted = function(muted){ - if (muted !== undefined) { - this.techCall('setMuted', muted); - return this; - } - return this.techGet('muted') || false; // Default to false -}; + Component.prototype.emitTapEvents = function emitTapEvents() { + // Track the start time so we can determine how long the touch lasted + var touchStart = 0; + var firstTouch = null; + + // Maximum movement allowed during a touch event to still be considered a tap + // Other popular libs use anywhere from 2 (hammer.js) to 15, so 10 seems like a nice, round number. + var tapMovementThreshold = 10; + + // The maximum length a touch can be while still being considered a tap + var touchTimeThreshold = 200; + + var couldBeTap = undefined; + + this.on('touchstart', function (event) { + // If more than one finger, don't consider treating this as a click + if (event.touches.length === 1) { + // Copy the touches object to prevent modifying the original + firstTouch = _objectAssign2['default']({}, event.touches[0]); + // Record start time so we can detect a tap vs. "touch and hold" + touchStart = new Date().getTime(); + // Reset couldBeTap tracking + couldBeTap = true; + } + }); -// Check if current tech can support native fullscreen -// (e.g. with built in controls like iOS, so not our flash swf) -vjs.Player.prototype.supportsFullScreen = function(){ - return this.techGet('supportsFullScreen') || false; -}; + this.on('touchmove', function (event) { + // If more than one finger, don't consider treating this as a click + if (event.touches.length > 1) { + couldBeTap = false; + } else if (firstTouch) { + // Some devices will throw touchmoves for all but the slightest of taps. + // So, if we moved only a small distance, this could still be a tap + var xdiff = event.touches[0].pageX - firstTouch.pageX; + var ydiff = event.touches[0].pageY - firstTouch.pageY; + var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff); + + if (touchDistance > tapMovementThreshold) { + couldBeTap = false; + } + } + }); -/** - * is the player in fullscreen - * @type {Boolean} - * @private - */ -vjs.Player.prototype.isFullscreen_ = false; + var noTap = function noTap() { + couldBeTap = false; + }; -/** - * Check if the player is in fullscreen mode - * - * // get - * var fullscreenOrNot = myPlayer.isFullscreen(); - * - * // set - * myPlayer.isFullscreen(true); // tell the player it's in fullscreen - * - * NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official - * property and instead document.fullscreenElement is used. But isFullscreen is - * still a valuable property for internal player workings. - * - * @param {Boolean=} isFS Update the player's fullscreen state - * @return {Boolean} true if fullscreen, false if not - * @return {vjs.Player} self, when setting - */ -vjs.Player.prototype.isFullscreen = function(isFS){ - if (isFS !== undefined) { - this.isFullscreen_ = !!isFS; - return this; - } - return this.isFullscreen_; -}; + // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s + this.on('touchleave', noTap); + this.on('touchcancel', noTap); + + // When the touch ends, measure how long it took and trigger the appropriate + // event + this.on('touchend', function (event) { + firstTouch = null; + // Proceed only if the touchmove/leave/cancel event didn't happen + if (couldBeTap === true) { + // Measure how long the touch lasted + var touchTime = new Date().getTime() - touchStart; + + // Make sure the touch was less than the threshold to be considered a tap + if (touchTime < touchTimeThreshold) { + // Don't let browser turn this into a click + event.preventDefault(); + this.trigger('tap'); + // It may be good to copy the touchend event object and change the + // type to tap, if the other event properties aren't exact after + // Events.fixEvent runs (e.g. event.target) + } + } + }); + }; -/** - * Old naming for isFullscreen() - * @deprecated for lowercase 's' version - */ -vjs.Player.prototype.isFullScreen = function(isFS){ - vjs.log.warn('player.isFullScreen() has been deprecated, use player.isFullscreen() with a lowercase "s")'); - return this.isFullscreen(isFS); -}; + /** + * Report user touch activity when touch events occur + * User activity is used to determine when controls should show/hide. It's + * relatively simple when it comes to mouse events, because any mouse event + * should show the controls. So we capture mouse events that bubble up to the + * player and report activity when that happens. + * With touch events it isn't as easy. We can't rely on touch events at the + * player level, because a tap (touchstart + touchend) on the video itself on + * mobile devices is meant to turn controls off (and on). User activity is + * checked asynchronously, so what could happen is a tap event on the video + * turns the controls off, then the touchend event bubbles up to the player, + * which if it reported user activity, would turn the controls right back on. + * (We also don't want to completely block touch events from bubbling up) + * Also a touchmove, touch+hold, and anything other than a tap is not supposed + * to turn the controls back on on a mobile device. + * Here we're setting the default component behavior to report user activity + * whenever touch events happen, and this can be turned off by components that + * want touch events to act differently. + * + * @method enableTouchActivity + */ -/** - * Increase the size of the video to full screen - * - * myPlayer.requestFullscreen(); - * - * In some browsers, full screen is not supported natively, so it enters - * "full window mode", where the video fills the browser window. - * In browsers and devices that support native full screen, sometimes the - * browser's default controls will be shown, and not the Video.js custom skin. - * This includes most mobile devices (iOS, Android) and older versions of - * Safari. - * - * @return {vjs.Player} self - */ -vjs.Player.prototype.requestFullscreen = function(){ - var fsApi = vjs.browser.fullscreenAPI; + Component.prototype.enableTouchActivity = function enableTouchActivity() { + // Don't continue if the root player doesn't support reporting user activity + if (!this.player() || !this.player().reportUserActivity) { + return; + } - this.isFullscreen(true); + // listener for reporting that the user is active + var report = Fn.bind(this.player(), this.player().reportUserActivity); - if (fsApi) { - // the browser supports going fullscreen at the element level so we can - // take the controls fullscreen as well as the video + var touchHolding = undefined; - // Trigger fullscreenchange event after change - // We have to specifically add this each time, and remove - // when canceling fullscreen. Otherwise if there's multiple - // players on a page, they would all be reacting to the same fullscreen - // events - vjs.on(document, fsApi['fullscreenchange'], vjs.bind(this, function(e){ - this.isFullscreen(document[fsApi.fullscreenElement]); + this.on('touchstart', function () { + report(); + // For as long as the they are touching the device or have their mouse down, + // we consider them active even if they're not moving their finger or mouse. + // So we want to continue to update that they are active + this.clearInterval(touchHolding); + // report at the same interval as activityCheck + touchHolding = this.setInterval(report, 250); + }); - // If cancelling fullscreen, remove event listener. - if (this.isFullscreen() === false) { - vjs.off(document, fsApi['fullscreenchange'], arguments.callee); - } + var touchEnd = function touchEnd(event) { + report(); + // stop the interval that maintains activity if the touch is holding + this.clearInterval(touchHolding); + }; - this.trigger('fullscreenchange'); - })); + this.on('touchmove', report); + this.on('touchend', touchEnd); + this.on('touchcancel', touchEnd); + }; - this.el_[fsApi.requestFullscreen](); + /** + * Creates timeout and sets up disposal automatically. + * + * @param {Function} fn The function to run after the timeout. + * @param {Number} timeout Number of ms to delay before executing specified function. + * @return {Number} Returns the timeout ID + * @method setTimeout + */ - } else if (this.tech.supportsFullScreen()) { - // we can't take the video.js controls fullscreen but we can go fullscreen - // with native controls - this.techCall('enterFullScreen'); - } else { - // fullscreen isn't supported so we'll just stretch the video element to - // fill the viewport - this.enterFullWindow(); - this.trigger('fullscreenchange'); - } + Component.prototype.setTimeout = function setTimeout(fn, timeout) { + fn = Fn.bind(this, fn); - return this; -}; + // window.setTimeout would be preferable here, but due to some bizarre issue with Sinon and/or Phantomjs, we can't. + var timeoutId = _globalWindow2['default'].setTimeout(fn, timeout); -/** - * Old naming for requestFullscreen - * @deprecated for lower case 's' version - */ -vjs.Player.prototype.requestFullScreen = function(){ - vjs.log.warn('player.requestFullScreen() has been deprecated, use player.requestFullscreen() with a lowercase "s")'); - return this.requestFullscreen(); -}; + var disposeFn = function disposeFn() { + this.clearTimeout(timeoutId); + }; + disposeFn.guid = 'vjs-timeout-' + timeoutId; -/** - * Return the video to its normal size after having been in full screen mode - * - * myPlayer.exitFullscreen(); - * - * @return {vjs.Player} self - */ -vjs.Player.prototype.exitFullscreen = function(){ - var fsApi = vjs.browser.fullscreenAPI; - this.isFullscreen(false); + this.on('dispose', disposeFn); - // Check for browser element fullscreen support - if (fsApi) { - document[fsApi.exitFullscreen](); - } else if (this.tech.supportsFullScreen()) { - this.techCall('exitFullScreen'); - } else { - this.exitFullWindow(); - this.trigger('fullscreenchange'); - } + return timeoutId; + }; - return this; -}; + /** + * Clears a timeout and removes the associated dispose listener + * + * @param {Number} timeoutId The id of the timeout to clear + * @return {Number} Returns the timeout ID + * @method clearTimeout + */ -/** - * Old naming for exitFullscreen - * @deprecated for exitFullscreen - */ -vjs.Player.prototype.cancelFullScreen = function(){ - vjs.log.warn('player.cancelFullScreen() has been deprecated, use player.exitFullscreen()'); - return this.exitFullscreen(); -}; + Component.prototype.clearTimeout = function clearTimeout(timeoutId) { + _globalWindow2['default'].clearTimeout(timeoutId); -// When fullscreen isn't supported we can stretch the video container to as wide as the browser will let us. -vjs.Player.prototype.enterFullWindow = function(){ - this.isFullWindow = true; + var disposeFn = function disposeFn() {}; - // Storing original doc overflow value to return to when fullscreen is off - this.docOrigOverflow = document.documentElement.style.overflow; + disposeFn.guid = 'vjs-timeout-' + timeoutId; - // Add listener for esc key to exit fullscreen - vjs.on(document, 'keydown', vjs.bind(this, this.fullWindowOnEscKey)); + this.off('dispose', disposeFn); - // Hide any scroll bars - document.documentElement.style.overflow = 'hidden'; + return timeoutId; + }; - // Apply fullscreen styles - vjs.addClass(document.body, 'vjs-full-window'); + /** + * Creates an interval and sets up disposal automatically. + * + * @param {Function} fn The function to run every N seconds. + * @param {Number} interval Number of ms to delay before executing specified function. + * @return {Number} Returns the interval ID + * @method setInterval + */ - this.trigger('enterFullWindow'); -}; -vjs.Player.prototype.fullWindowOnEscKey = function(event){ - if (event.keyCode === 27) { - if (this.isFullscreen() === true) { - this.exitFullscreen(); - } else { - this.exitFullWindow(); - } - } -}; + Component.prototype.setInterval = function setInterval(fn, interval) { + fn = Fn.bind(this, fn); -vjs.Player.prototype.exitFullWindow = function(){ - this.isFullWindow = false; - vjs.off(document, 'keydown', this.fullWindowOnEscKey); + var intervalId = _globalWindow2['default'].setInterval(fn, interval); - // Unhide scroll bars. - document.documentElement.style.overflow = this.docOrigOverflow; + var disposeFn = function disposeFn() { + this.clearInterval(intervalId); + }; - // Remove fullscreen styles - vjs.removeClass(document.body, 'vjs-full-window'); + disposeFn.guid = 'vjs-interval-' + intervalId; - // Resize the box, controller, and poster to original sizes - // this.positionAll(); - this.trigger('exitFullWindow'); -}; + this.on('dispose', disposeFn); -vjs.Player.prototype.selectSource = function(sources){ - // Loop through each playback technology in the options order - for (var i=0,j=this.options_['techOrder'];i 0) { - // In milliseconds, if no more activity has occurred the - // user will be considered inactive - inactivityTimeout = this.setTimeout(function () { - // Protect against the case where the inactivityTimeout can trigger just - // before the next user activity is picked up by the activityCheck loop - // causing a flicker - if (!this.userActivity_) { - this.userActive(false); - } - }, timeout); + /** + * Handle click on audio track + * + * @method handleClick + */ + + AudioTrackMenuItem.prototype.handleClick = function handleClick(event) { + var tracks = this.player_.audioTracks(); + + _MenuItem.prototype.handleClick.call(this, event); + + if (!tracks) return; + + for (var i = 0; i < tracks.length; i++) { + var track = tracks[i]; + + if (track === this.track) { + track.enabled = true; } } - }, 250); -}; + }; -/** - * Gets or sets the current playback rate. - * @param {Boolean} rate New playback rate to set. - * @return {Number} Returns the new playback rate when setting - * @return {Number} Returns the current playback rate when getting - */ -vjs.Player.prototype.playbackRate = function(rate) { - if (rate !== undefined) { - this.techCall('setPlaybackRate', rate); - return this; - } + /** + * Handle audio track change + * + * @method handleTracksChange + */ - if (this.tech && this.tech['featuresPlaybackRate']) { - return this.techGet('playbackRate'); - } else { - return 1.0; - } + AudioTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) { + this.selected(this.track.enabled); + }; -}; + return AudioTrackMenuItem; +})(_menuMenuItemJs2['default']); -/** - * Store the current audio state - * @type {Boolean} - * @private - */ -vjs.Player.prototype.isAudio_ = false; +_componentJs2['default'].registerComponent('AudioTrackMenuItem', AudioTrackMenuItem); +exports['default'] = AudioTrackMenuItem; +module.exports = exports['default']; +},{"../../component.js":67,"../../menu/menu-item.js":110,"../../utils/fn.js":144}],70:[function(_dereq_,module,exports){ /** - * Gets or sets the audio flag - * - * @param {Boolean} bool True signals that this is an audio player. - * @return {Boolean} Returns true if player is audio, false if not when getting - * @return {vjs.Player} Returns the player if setting - * @private + * @file control-bar.js */ -vjs.Player.prototype.isAudio = function(bool) { - if (bool !== undefined) { - this.isAudio_ = !!bool; - return this; - } +'use strict'; - return this.isAudio_; -}; +exports.__esModule = true; -/** - * Returns the current state of network activity for the element, from - * the codes in the list below. - * - NETWORK_EMPTY (numeric value 0) - * The element has not yet been initialised. All attributes are in - * their initial states. - * - NETWORK_IDLE (numeric value 1) - * The element's resource selection algorithm is active and has - * selected a resource, but it is not actually using the network at - * this time. - * - NETWORK_LOADING (numeric value 2) - * The user agent is actively trying to download data. - * - NETWORK_NO_SOURCE (numeric value 3) - * The element's resource selection algorithm is active, but it has - * not yet found a resource to use. - * @return {Number} the current network activity state - * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states - */ -vjs.Player.prototype.networkState = function(){ - return this.techGet('networkState'); -}; +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } -/** - * Returns a value that expresses the current state of the element - * with respect to rendering the current playback position, from the - * codes in the list below. - * - HAVE_NOTHING (numeric value 0) - * No information regarding the media resource is available. - * - HAVE_METADATA (numeric value 1) - * Enough of the resource has been obtained that the duration of the - * resource is available. - * - HAVE_CURRENT_DATA (numeric value 2) - * Data for the immediate current playback position is available. - * - HAVE_FUTURE_DATA (numeric value 3) - * Data for the immediate current playback position is available, as - * well as enough data for the user agent to advance the current - * playback position in the direction of playback. - * - HAVE_ENOUGH_DATA (numeric value 4) - * The user agent estimates that enough data is available for - * playback to proceed uninterrupted. - * @return {Number} the current playback rendering state - * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate - */ -vjs.Player.prototype.readyState = function(){ - return this.techGet('readyState'); -}; +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } -/** - * Text tracks are tracks of timed text events. - * Captions - text displayed over the video for the hearing impaired - * Subtitles - text displayed over the video for those who don't understand language in the video - * Chapters - text displayed in a menu allowing the user to jump to particular points (chapters) in the video - * Descriptions (not supported yet) - audio descriptions that are read back to the user by a screen reading device - */ +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } -/** - * Get an array of associated text tracks. captions, subtitles, chapters, descriptions - * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks - * @return {Array} Array of track objects - */ -vjs.Player.prototype.textTracks = function(){ - // cannot use techGet directly because it checks to see whether the tech is ready. - // Flash is unlikely to be ready in time but textTracks should still work. - return this.tech && this.tech['textTracks'](); -}; +var _componentJs = _dereq_('../component.js'); -vjs.Player.prototype.remoteTextTracks = function() { - return this.tech && this.tech['remoteTextTracks'](); -}; +var _componentJs2 = _interopRequireDefault(_componentJs); -/** - * Add a text track - * In addition to the W3C settings we allow adding additional info through options. - * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack - * @param {String} kind Captions, subtitles, chapters, descriptions, or metadata - * @param {String=} label Optional label - * @param {String=} language Optional language - */ -vjs.Player.prototype.addTextTrack = function(kind, label, language) { - return this.tech && this.tech['addTextTrack'](kind, label, language); -}; +// Required children -vjs.Player.prototype.addRemoteTextTrack = function(options) { - return this.tech && this.tech['addRemoteTextTrack'](options); -}; +var _playToggleJs = _dereq_('./play-toggle.js'); -vjs.Player.prototype.removeRemoteTextTrack = function(track) { - this.tech && this.tech['removeRemoteTextTrack'](track); -}; +var _playToggleJs2 = _interopRequireDefault(_playToggleJs); -// Methods to add support for -// initialTime: function(){ return this.techCall('initialTime'); }, -// startOffsetTime: function(){ return this.techCall('startOffsetTime'); }, -// played: function(){ return this.techCall('played'); }, -// seekable: function(){ return this.techCall('seekable'); }, -// videoTracks: function(){ return this.techCall('videoTracks'); }, -// audioTracks: function(){ return this.techCall('audioTracks'); }, -// videoWidth: function(){ return this.techCall('videoWidth'); }, -// videoHeight: function(){ return this.techCall('videoHeight'); }, -// defaultPlaybackRate: function(){ return this.techCall('defaultPlaybackRate'); }, -// mediaGroup: function(){ return this.techCall('mediaGroup'); }, -// controller: function(){ return this.techCall('controller'); }, -// defaultMuted: function(){ return this.techCall('defaultMuted'); } - -// TODO -// currentSrcList: the array of sources including other formats and bitrates -// playList: array of source lists in order of playback -/** - * Container of main controls - * @param {vjs.Player|Object} player - * @param {Object=} options - * @class - * @constructor - * @extends vjs.Component - */ -vjs.ControlBar = vjs.Component.extend(); - -vjs.ControlBar.prototype.options_ = { - loadEvent: 'play', - children: { - 'playToggle': {}, - 'currentTimeDisplay': {}, - 'timeDivider': {}, - 'durationDisplay': {}, - 'remainingTimeDisplay': {}, - 'liveDisplay': {}, - 'progressControl': {}, - 'fullscreenToggle': {}, - 'volumeControl': {}, - 'muteToggle': {}, - // 'volumeMenuButton': {}, - 'playbackRateMenuButton': {}, - 'subtitlesButton': {}, - 'captionsButton': {}, - 'chaptersButton': {} - } -}; +var _timeControlsCurrentTimeDisplayJs = _dereq_('./time-controls/current-time-display.js'); -vjs.ControlBar.prototype.createEl = function(){ - return vjs.createEl('div', { - className: 'vjs-control-bar' - }); -}; -/** - * Displays the live indicator - * TODO - Future make it click to snap to live - * @param {vjs.Player|Object} player - * @param {Object=} options - * @constructor - */ -vjs.LiveDisplay = vjs.Component.extend({ - init: function(player, options){ - vjs.Component.call(this, player, options); - } -}); +var _timeControlsCurrentTimeDisplayJs2 = _interopRequireDefault(_timeControlsCurrentTimeDisplayJs); -vjs.LiveDisplay.prototype.createEl = function(){ - var el = vjs.Component.prototype.createEl.call(this, 'div', { - className: 'vjs-live-controls vjs-control' - }); +var _timeControlsDurationDisplayJs = _dereq_('./time-controls/duration-display.js'); - this.contentEl_ = vjs.createEl('div', { - className: 'vjs-live-display', - innerHTML: '' + this.localize('Stream Type') + '' + this.localize('LIVE'), - 'aria-live': 'off' - }); +var _timeControlsDurationDisplayJs2 = _interopRequireDefault(_timeControlsDurationDisplayJs); - el.appendChild(this.contentEl_); +var _timeControlsTimeDividerJs = _dereq_('./time-controls/time-divider.js'); - return el; -}; -/** - * Button to toggle between play and pause - * @param {vjs.Player|Object} player - * @param {Object=} options - * @class - * @constructor - */ -vjs.PlayToggle = vjs.Button.extend({ - /** @constructor */ - init: function(player, options){ - vjs.Button.call(this, player, options); +var _timeControlsTimeDividerJs2 = _interopRequireDefault(_timeControlsTimeDividerJs); - this.on(player, 'play', this.onPlay); - this.on(player, 'pause', this.onPause); - } -}); +var _timeControlsRemainingTimeDisplayJs = _dereq_('./time-controls/remaining-time-display.js'); -vjs.PlayToggle.prototype.buttonText = 'Play'; +var _timeControlsRemainingTimeDisplayJs2 = _interopRequireDefault(_timeControlsRemainingTimeDisplayJs); -vjs.PlayToggle.prototype.buildCSSClass = function(){ - return 'vjs-play-control ' + vjs.Button.prototype.buildCSSClass.call(this); -}; +var _liveDisplayJs = _dereq_('./live-display.js'); -// OnClick - Toggle between play and pause -vjs.PlayToggle.prototype.onClick = function(){ - if (this.player_.paused()) { - this.player_.play(); - } else { - this.player_.pause(); - } -}; +var _liveDisplayJs2 = _interopRequireDefault(_liveDisplayJs); - // OnPlay - Add the vjs-playing class to the element so it can change appearance -vjs.PlayToggle.prototype.onPlay = function(){ - this.removeClass('vjs-paused'); - this.addClass('vjs-playing'); - this.el_.children[0].children[0].innerHTML = this.localize('Pause'); // change the button text to "Pause" -}; +var _progressControlProgressControlJs = _dereq_('./progress-control/progress-control.js'); - // OnPause - Add the vjs-paused class to the element so it can change appearance -vjs.PlayToggle.prototype.onPause = function(){ - this.removeClass('vjs-playing'); - this.addClass('vjs-paused'); - this.el_.children[0].children[0].innerHTML = this.localize('Play'); // change the button text to "Play" -}; -/** - * Displays the current time - * @param {vjs.Player|Object} player - * @param {Object=} options - * @constructor - */ -vjs.CurrentTimeDisplay = vjs.Component.extend({ - /** @constructor */ - init: function(player, options){ - vjs.Component.call(this, player, options); +var _progressControlProgressControlJs2 = _interopRequireDefault(_progressControlProgressControlJs); - this.on(player, 'timeupdate', this.updateContent); - } -}); +var _fullscreenToggleJs = _dereq_('./fullscreen-toggle.js'); -vjs.CurrentTimeDisplay.prototype.createEl = function(){ - var el = vjs.Component.prototype.createEl.call(this, 'div', { - className: 'vjs-current-time vjs-time-controls vjs-control' - }); +var _fullscreenToggleJs2 = _interopRequireDefault(_fullscreenToggleJs); - this.contentEl_ = vjs.createEl('div', { - className: 'vjs-current-time-display', - innerHTML: 'Current Time ' + '0:00', // label the current time for screen reader users - 'aria-live': 'off' // tell screen readers not to automatically read the time as it changes - }); +var _volumeControlVolumeControlJs = _dereq_('./volume-control/volume-control.js'); - el.appendChild(this.contentEl_); - return el; -}; +var _volumeControlVolumeControlJs2 = _interopRequireDefault(_volumeControlVolumeControlJs); -vjs.CurrentTimeDisplay.prototype.updateContent = function(){ - // Allows for smooth scrubbing, when player can't keep up. - var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime(); - this.contentEl_.innerHTML = '' + this.localize('Current Time') + ' ' + vjs.formatTime(time, this.player_.duration()); -}; +var _volumeMenuButtonJs = _dereq_('./volume-menu-button.js'); -/** - * Displays the duration - * @param {vjs.Player|Object} player - * @param {Object=} options - * @constructor - */ -vjs.DurationDisplay = vjs.Component.extend({ - /** @constructor */ - init: function(player, options){ - vjs.Component.call(this, player, options); +var _volumeMenuButtonJs2 = _interopRequireDefault(_volumeMenuButtonJs); - // this might need to be changed 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. - this.on(player, 'timeupdate', this.updateContent); - this.on(player, 'loadedmetadata', this.updateContent); - } -}); +var _muteToggleJs = _dereq_('./mute-toggle.js'); -vjs.DurationDisplay.prototype.createEl = function(){ - var el = vjs.Component.prototype.createEl.call(this, 'div', { - className: 'vjs-duration vjs-time-controls vjs-control' - }); +var _muteToggleJs2 = _interopRequireDefault(_muteToggleJs); - this.contentEl_ = vjs.createEl('div', { - className: 'vjs-duration-display', - innerHTML: '' + this.localize('Duration Time') + ' ' + '0:00', // label the duration time for screen reader users - 'aria-live': 'off' // tell screen readers not to automatically read the time as it changes - }); +var _textTrackControlsChaptersButtonJs = _dereq_('./text-track-controls/chapters-button.js'); - el.appendChild(this.contentEl_); - return el; -}; +var _textTrackControlsChaptersButtonJs2 = _interopRequireDefault(_textTrackControlsChaptersButtonJs); -vjs.DurationDisplay.prototype.updateContent = function(){ - var duration = this.player_.duration(); - if (duration) { - this.contentEl_.innerHTML = '' + this.localize('Duration Time') + ' ' + vjs.formatTime(duration); // label the duration time for screen reader users - } -}; +var _textTrackControlsDescriptionsButtonJs = _dereq_('./text-track-controls/descriptions-button.js'); -/** - * The separator between the current time and duration - * - * Can be hidden if it's not needed in the design. - * - * @param {vjs.Player|Object} player - * @param {Object=} options - * @constructor - */ -vjs.TimeDivider = vjs.Component.extend({ - /** @constructor */ - init: function(player, options){ - vjs.Component.call(this, player, options); - } -}); +var _textTrackControlsDescriptionsButtonJs2 = _interopRequireDefault(_textTrackControlsDescriptionsButtonJs); -vjs.TimeDivider.prototype.createEl = function(){ - return vjs.Component.prototype.createEl.call(this, 'div', { - className: 'vjs-time-divider', - innerHTML: '
    /
    ' - }); -}; +var _textTrackControlsSubtitlesButtonJs = _dereq_('./text-track-controls/subtitles-button.js'); -/** - * Displays the time left in the video - * @param {vjs.Player|Object} player - * @param {Object=} options - * @constructor - */ -vjs.RemainingTimeDisplay = vjs.Component.extend({ - /** @constructor */ - init: function(player, options){ - vjs.Component.call(this, player, options); +var _textTrackControlsSubtitlesButtonJs2 = _interopRequireDefault(_textTrackControlsSubtitlesButtonJs); - this.on(player, 'timeupdate', this.updateContent); - } -}); +var _textTrackControlsCaptionsButtonJs = _dereq_('./text-track-controls/captions-button.js'); -vjs.RemainingTimeDisplay.prototype.createEl = function(){ - var el = vjs.Component.prototype.createEl.call(this, 'div', { - className: 'vjs-remaining-time vjs-time-controls vjs-control' - }); +var _textTrackControlsCaptionsButtonJs2 = _interopRequireDefault(_textTrackControlsCaptionsButtonJs); - this.contentEl_ = vjs.createEl('div', { - className: 'vjs-remaining-time-display', - innerHTML: '' + this.localize('Remaining Time') + ' ' + '-0:00', // label the remaining time for screen reader users - 'aria-live': 'off' // tell screen readers not to automatically read the time as it changes - }); +var _audioTrackControlsAudioTrackButtonJs = _dereq_('./audio-track-controls/audio-track-button.js'); - el.appendChild(this.contentEl_); - return el; -}; +var _audioTrackControlsAudioTrackButtonJs2 = _interopRequireDefault(_audioTrackControlsAudioTrackButtonJs); -vjs.RemainingTimeDisplay.prototype.updateContent = function(){ - if (this.player_.duration()) { - this.contentEl_.innerHTML = '' + this.localize('Remaining Time') + ' ' + '-'+ vjs.formatTime(this.player_.remainingTime()); - } +var _playbackRateMenuPlaybackRateMenuButtonJs = _dereq_('./playback-rate-menu/playback-rate-menu-button.js'); - // Allows for smooth scrubbing, when player can't keep up. - // var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime(); - // this.contentEl_.innerHTML = vjs.formatTime(time, this.player_.duration()); -}; -/** - * Toggle fullscreen video - * @param {vjs.Player|Object} player - * @param {Object=} options - * @class - * @extends vjs.Button - */ -vjs.FullscreenToggle = vjs.Button.extend({ - /** - * @constructor - * @memberof vjs.FullscreenToggle - * @instance - */ - init: function(player, options){ - vjs.Button.call(this, player, options); - } -}); +var _playbackRateMenuPlaybackRateMenuButtonJs2 = _interopRequireDefault(_playbackRateMenuPlaybackRateMenuButtonJs); -vjs.FullscreenToggle.prototype.buttonText = 'Fullscreen'; +var _spacerControlsCustomControlSpacerJs = _dereq_('./spacer-controls/custom-control-spacer.js'); -vjs.FullscreenToggle.prototype.buildCSSClass = function(){ - return 'vjs-fullscreen-control ' + vjs.Button.prototype.buildCSSClass.call(this); -}; +var _spacerControlsCustomControlSpacerJs2 = _interopRequireDefault(_spacerControlsCustomControlSpacerJs); -vjs.FullscreenToggle.prototype.onClick = function(){ - if (!this.player_.isFullscreen()) { - this.player_.requestFullscreen(); - this.controlText_.innerHTML = this.localize('Non-Fullscreen'); - } else { - this.player_.exitFullscreen(); - this.controlText_.innerHTML = this.localize('Fullscreen'); - } -}; /** - * The Progress Control component contains the seek bar, load progress, - * and play progress + * Container of main controls * - * @param {vjs.Player|Object} player - * @param {Object=} options - * @constructor + * @extends Component + * @class ControlBar */ -vjs.ProgressControl = vjs.Component.extend({ - /** @constructor */ - init: function(player, options){ - vjs.Component.call(this, player, options); - } -}); -vjs.ProgressControl.prototype.options_ = { - children: { - 'seekBar': {} - } -}; +var ControlBar = (function (_Component) { + _inherits(ControlBar, _Component); -vjs.ProgressControl.prototype.createEl = function(){ - return vjs.Component.prototype.createEl.call(this, 'div', { - className: 'vjs-progress-control vjs-control' - }); -}; + function ControlBar() { + _classCallCheck(this, ControlBar); -/** - * Seek Bar and holder for the progress bars - * - * @param {vjs.Player|Object} player - * @param {Object=} options - * @constructor - */ -vjs.SeekBar = vjs.Slider.extend({ - /** @constructor */ - init: function(player, options){ - vjs.Slider.call(this, player, options); - this.on(player, 'timeupdate', this.updateARIAAttributes); - player.ready(vjs.bind(this, this.updateARIAAttributes)); + _Component.apply(this, arguments); } -}); -vjs.SeekBar.prototype.options_ = { - children: { - 'loadProgressBar': {}, - 'playProgressBar': {}, - 'seekHandle': {} - }, - 'barName': 'playProgressBar', - 'handleName': 'seekHandle' -}; - -vjs.SeekBar.prototype.playerEvent = 'timeupdate'; + /** + * Create the component's DOM element + * + * @return {Element} + * @method createEl + */ -vjs.SeekBar.prototype.createEl = function(){ - return vjs.Slider.prototype.createEl.call(this, 'div', { - className: 'vjs-progress-holder', - 'aria-label': 'video progress bar' - }); -}; + ControlBar.prototype.createEl = function createEl() { + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-control-bar', + dir: 'ltr' + }, { + 'role': 'group' // The control bar is a group, so it can contain menuitems + }); + }; -vjs.SeekBar.prototype.updateARIAAttributes = function(){ - // Allows for smooth scrubbing, when player can't keep up. - var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime(); - this.el_.setAttribute('aria-valuenow',vjs.round(this.getPercent()*100, 2)); // machine readable value of progress bar (percentage complete) - this.el_.setAttribute('aria-valuetext',vjs.formatTime(time, this.player_.duration())); // human readable value of progress bar (time complete) -}; + return ControlBar; +})(_componentJs2['default']); -vjs.SeekBar.prototype.getPercent = function(){ - return this.player_.currentTime() / this.player_.duration(); +ControlBar.prototype.options_ = { + children: ['playToggle', 'volumeMenuButton', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subtitlesButton', 'captionsButton', 'audioTrackButton', 'fullscreenToggle'] }; -vjs.SeekBar.prototype.onMouseDown = function(event){ - vjs.Slider.prototype.onMouseDown.call(this, event); +_componentJs2['default'].registerComponent('ControlBar', ControlBar); +exports['default'] = ControlBar; +module.exports = exports['default']; - this.player_.scrubbing = true; - this.player_.addClass('vjs-scrubbing'); +},{"../component.js":67,"./audio-track-controls/audio-track-button.js":68,"./fullscreen-toggle.js":71,"./live-display.js":72,"./mute-toggle.js":73,"./play-toggle.js":74,"./playback-rate-menu/playback-rate-menu-button.js":75,"./progress-control/progress-control.js":80,"./spacer-controls/custom-control-spacer.js":83,"./text-track-controls/captions-button.js":86,"./text-track-controls/chapters-button.js":87,"./text-track-controls/descriptions-button.js":89,"./text-track-controls/subtitles-button.js":91,"./time-controls/current-time-display.js":94,"./time-controls/duration-display.js":95,"./time-controls/remaining-time-display.js":96,"./time-controls/time-divider.js":97,"./volume-control/volume-control.js":100,"./volume-menu-button.js":102}],71:[function(_dereq_,module,exports){ +/** + * @file fullscreen-toggle.js + */ +'use strict'; - this.videoWasPlaying = !this.player_.paused(); - this.player_.pause(); -}; +exports.__esModule = true; -vjs.SeekBar.prototype.onMouseMove = function(event){ - var newTime = this.calculateDistance(event) * this.player_.duration(); +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - // Don't let video end while scrubbing. - if (newTime == this.player_.duration()) { newTime = newTime - 0.1; } +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - // Set new time (tell player to seek to new time) - this.player_.currentTime(newTime); -}; +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } -vjs.SeekBar.prototype.onMouseUp = function(event){ - vjs.Slider.prototype.onMouseUp.call(this, event); +var _buttonJs = _dereq_('../button.js'); - this.player_.scrubbing = false; - this.player_.removeClass('vjs-scrubbing'); - if (this.videoWasPlaying) { - this.player_.play(); - } -}; +var _buttonJs2 = _interopRequireDefault(_buttonJs); -vjs.SeekBar.prototype.stepForward = function(){ - this.player_.currentTime(this.player_.currentTime() + 5); // more quickly fast forward for keyboard-only users -}; +var _componentJs = _dereq_('../component.js'); -vjs.SeekBar.prototype.stepBack = function(){ - this.player_.currentTime(this.player_.currentTime() - 5); // more quickly rewind for keyboard-only users -}; +var _componentJs2 = _interopRequireDefault(_componentJs); /** - * Shows load progress + * Toggle fullscreen video * - * @param {vjs.Player|Object} player - * @param {Object=} options - * @constructor + * @extends Button + * @class FullscreenToggle */ -vjs.LoadProgressBar = vjs.Component.extend({ - /** @constructor */ - init: function(player, options){ - vjs.Component.call(this, player, options); - this.on(player, 'progress', this.update); - } -}); -vjs.LoadProgressBar.prototype.createEl = function(){ - return vjs.Component.prototype.createEl.call(this, 'div', { - className: 'vjs-load-progress', - innerHTML: '' + this.localize('Loaded') + ': 0%' - }); -}; +var FullscreenToggle = (function (_Button) { + _inherits(FullscreenToggle, _Button); -vjs.LoadProgressBar.prototype.update = function(){ - var i, start, end, part, - buffered = this.player_.buffered(), - duration = this.player_.duration(), - bufferedEnd = this.player_.bufferedEnd(), - children = this.el_.children, - // get the percent width of a time compared to the total end - percentify = function (time, end){ - var percent = (time / end) || 0; // no NaN - return (percent * 100) + '%'; - }; + function FullscreenToggle() { + _classCallCheck(this, FullscreenToggle); + + _Button.apply(this, arguments); + } + + /** + * Allow sub components to stack CSS class names + * + * @return {String} The constructed class name + * @method buildCSSClass + */ - // update the width of the progress bar - this.el_.style.width = percentify(bufferedEnd, duration); + FullscreenToggle.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-fullscreen-control ' + _Button.prototype.buildCSSClass.call(this); + }; - // add child elements to represent the individual buffered time ranges - for (i = 0; i < buffered.length; i++) { - start = buffered.start(i), - end = buffered.end(i), - part = children[i]; + /** + * Handles click for full screen + * + * @method handleClick + */ - if (!part) { - part = this.el_.appendChild(vjs.createEl()); + FullscreenToggle.prototype.handleClick = function handleClick() { + if (!this.player_.isFullscreen()) { + this.player_.requestFullscreen(); + this.controlText('Non-Fullscreen'); + } else { + this.player_.exitFullscreen(); + this.controlText('Fullscreen'); } + }; - // set the percent based on the width of the progress bar (bufferedEnd) - part.style.left = percentify(start, bufferedEnd); - part.style.width = percentify(end - start, bufferedEnd); - } + return FullscreenToggle; +})(_buttonJs2['default']); - // remove unused buffered range elements - for (i = children.length; i > buffered.length; i--) { - this.el_.removeChild(children[i-1]); - } -}; +FullscreenToggle.prototype.controlText_ = 'Fullscreen'; + +_componentJs2['default'].registerComponent('FullscreenToggle', FullscreenToggle); +exports['default'] = FullscreenToggle; +module.exports = exports['default']; +},{"../button.js":64,"../component.js":67}],72:[function(_dereq_,module,exports){ /** - * Shows play progress - * - * @param {vjs.Player|Object} player - * @param {Object=} options - * @constructor + * @file live-display.js */ -vjs.PlayProgressBar = vjs.Component.extend({ - /** @constructor */ - init: function(player, options){ - vjs.Component.call(this, player, options); - } -}); +'use strict'; -vjs.PlayProgressBar.prototype.createEl = function(){ - return vjs.Component.prototype.createEl.call(this, 'div', { - className: 'vjs-play-progress', - innerHTML: '' + this.localize('Progress') + ': 0%' - }); -}; +exports.__esModule = true; + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _component = _dereq_('../component'); + +var _component2 = _interopRequireDefault(_component); + +var _utilsDomJs = _dereq_('../utils/dom.js'); + +var Dom = _interopRequireWildcard(_utilsDomJs); /** - * The Seek Handle shows the current position of the playhead during playback, - * and can be dragged to adjust the playhead. + * Displays the live indicator + * TODO - Future make it click to snap to live * - * @param {vjs.Player|Object} player - * @param {Object=} options - * @constructor + * @extends Component + * @class LiveDisplay */ -vjs.SeekHandle = vjs.SliderHandle.extend({ - init: function(player, options) { - vjs.SliderHandle.call(this, player, options); - this.on(player, 'timeupdate', this.updateContent); + +var LiveDisplay = (function (_Component) { + _inherits(LiveDisplay, _Component); + + function LiveDisplay(player, options) { + _classCallCheck(this, LiveDisplay); + + _Component.call(this, player, options); + + this.updateShowing(); + this.on(this.player(), 'durationchange', this.updateShowing); } -}); -/** - * The default value for the handle content, which may be read by screen readers - * - * @type {String} - * @private - */ -vjs.SeekHandle.prototype.defaultValue = '00:00'; + /** + * Create the component's DOM element + * + * @return {Element} + * @method createEl + */ -/** @inheritDoc */ -vjs.SeekHandle.prototype.createEl = function() { - return vjs.SliderHandle.prototype.createEl.call(this, 'div', { - className: 'vjs-seek-handle', - 'aria-live': 'off' - }); -}; + LiveDisplay.prototype.createEl = function createEl() { + var el = _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-live-control vjs-control' + }); -vjs.SeekHandle.prototype.updateContent = function() { - var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime(); - this.el_.innerHTML = '' + vjs.formatTime(time, this.player_.duration()) + ''; -}; -/** - * The component for controlling the volume level - * - * @param {vjs.Player|Object} player - * @param {Object=} options - * @constructor - */ -vjs.VolumeControl = vjs.Component.extend({ - /** @constructor */ - init: function(player, options){ - vjs.Component.call(this, player, options); + this.contentEl_ = Dom.createEl('div', { + className: 'vjs-live-display', + innerHTML: '' + this.localize('Stream Type') + '' + this.localize('LIVE') + }, { + 'aria-live': 'off' + }); - // hide volume controls when they're not supported by the current tech - if (player.tech && player.tech['featuresVolumeControl'] === false) { - this.addClass('vjs-hidden'); + el.appendChild(this.contentEl_); + return el; + }; + + LiveDisplay.prototype.updateShowing = function updateShowing() { + if (this.player().duration() === Infinity) { + this.show(); + } else { + this.hide(); } - this.on(player, 'loadstart', function(){ - if (player.tech['featuresVolumeControl'] === false) { - this.addClass('vjs-hidden'); - } else { - this.removeClass('vjs-hidden'); - } - }); - } -}); + }; -vjs.VolumeControl.prototype.options_ = { - children: { - 'volumeBar': {} - } -}; + return LiveDisplay; +})(_component2['default']); -vjs.VolumeControl.prototype.createEl = function(){ - return vjs.Component.prototype.createEl.call(this, 'div', { - className: 'vjs-volume-control vjs-control' - }); -}; +_component2['default'].registerComponent('LiveDisplay', LiveDisplay); +exports['default'] = LiveDisplay; +module.exports = exports['default']; +},{"../component":67,"../utils/dom.js":142}],73:[function(_dereq_,module,exports){ /** - * The bar that contains the volume level and can be clicked on to adjust the level - * - * @param {vjs.Player|Object} player - * @param {Object=} options - * @constructor + * @file mute-toggle.js */ -vjs.VolumeBar = vjs.Slider.extend({ - /** @constructor */ - init: function(player, options){ - vjs.Slider.call(this, player, options); - this.on(player, 'volumechange', this.updateARIAAttributes); - player.ready(vjs.bind(this, this.updateARIAAttributes)); - } -}); +'use strict'; -vjs.VolumeBar.prototype.updateARIAAttributes = function(){ - // Current value of volume bar as a percentage - this.el_.setAttribute('aria-valuenow',vjs.round(this.player_.volume()*100, 2)); - this.el_.setAttribute('aria-valuetext',vjs.round(this.player_.volume()*100, 2)+'%'); -}; +exports.__esModule = true; -vjs.VolumeBar.prototype.options_ = { - children: { - 'volumeLevel': {}, - 'volumeHandle': {} - }, - 'barName': 'volumeLevel', - 'handleName': 'volumeHandle' -}; +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } -vjs.VolumeBar.prototype.playerEvent = 'volumechange'; +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } -vjs.VolumeBar.prototype.createEl = function(){ - return vjs.Slider.prototype.createEl.call(this, 'div', { - className: 'vjs-volume-bar', - 'aria-label': 'volume level' - }); -}; +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } -vjs.VolumeBar.prototype.onMouseMove = function(event) { - if (this.player_.muted()) { - this.player_.muted(false); - } +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - this.player_.volume(this.calculateDistance(event)); -}; +var _button = _dereq_('../button'); -vjs.VolumeBar.prototype.getPercent = function(){ - if (this.player_.muted()) { - return 0; - } else { - return this.player_.volume(); - } -}; +var _button2 = _interopRequireDefault(_button); -vjs.VolumeBar.prototype.stepForward = function(){ - this.player_.volume(this.player_.volume() + 0.1); -}; +var _component = _dereq_('../component'); -vjs.VolumeBar.prototype.stepBack = function(){ - this.player_.volume(this.player_.volume() - 0.1); -}; +var _component2 = _interopRequireDefault(_component); -/** - * Shows volume level - * - * @param {vjs.Player|Object} player - * @param {Object=} options - * @constructor - */ -vjs.VolumeLevel = vjs.Component.extend({ - /** @constructor */ - init: function(player, options){ - vjs.Component.call(this, player, options); - } -}); +var _utilsDomJs = _dereq_('../utils/dom.js'); -vjs.VolumeLevel.prototype.createEl = function(){ - return vjs.Component.prototype.createEl.call(this, 'div', { - className: 'vjs-volume-level', - innerHTML: '' - }); -}; +var Dom = _interopRequireWildcard(_utilsDomJs); /** - * The volume handle can be dragged to adjust the volume level + * A button component for muting the audio * - * @param {vjs.Player|Object} player + * @param {Player|Object} player * @param {Object=} options - * @constructor + * @extends Button + * @class MuteToggle */ - vjs.VolumeHandle = vjs.SliderHandle.extend(); - vjs.VolumeHandle.prototype.defaultValue = '00:00'; +var MuteToggle = (function (_Button) { + _inherits(MuteToggle, _Button); - /** @inheritDoc */ - vjs.VolumeHandle.prototype.createEl = function(){ - return vjs.SliderHandle.prototype.createEl.call(this, 'div', { - className: 'vjs-volume-handle' - }); - }; -/** - * A button component for muting the audio - * - * @param {vjs.Player|Object} player - * @param {Object=} options - * @constructor - */ -vjs.MuteToggle = vjs.Button.extend({ - /** @constructor */ - init: function(player, options){ - vjs.Button.call(this, player, options); + function MuteToggle(player, options) { + _classCallCheck(this, MuteToggle); + + _Button.call(this, player, options); this.on(player, 'volumechange', this.update); // hide mute toggle if the current tech doesn't support volume control - if (player.tech && player.tech['featuresVolumeControl'] === false) { + if (player.tech_ && player.tech_['featuresVolumeControl'] === false) { this.addClass('vjs-hidden'); } - this.on(player, 'loadstart', function(){ - if (player.tech['featuresVolumeControl'] === false) { + this.on(player, 'loadstart', function () { + this.update(); // We need to update the button to account for a default muted state. + + if (player.tech_['featuresVolumeControl'] === false) { this.addClass('vjs-hidden'); } else { this.removeClass('vjs-hidden'); } }); } -}); -vjs.MuteToggle.prototype.createEl = function(){ - return vjs.Button.prototype.createEl.call(this, 'div', { - className: 'vjs-mute-control vjs-control', - innerHTML: '
    ' + this.localize('Mute') + '
    ' - }); -}; + /** + * Allow sub components to stack CSS class names + * + * @return {String} The constructed class name + * @method buildCSSClass + */ -vjs.MuteToggle.prototype.onClick = function(){ - this.player_.muted( this.player_.muted() ? false : true ); -}; + MuteToggle.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-mute-control ' + _Button.prototype.buildCSSClass.call(this); + }; -vjs.MuteToggle.prototype.update = function(){ - var vol = this.player_.volume(), - level = 3; + /** + * Handle click on mute + * + * @method handleClick + */ - if (vol === 0 || this.player_.muted()) { - level = 0; - } else if (vol < 0.33) { - level = 1; - } else if (vol < 0.67) { - level = 2; - } + MuteToggle.prototype.handleClick = function handleClick() { + this.player_.muted(this.player_.muted() ? false : true); + }; - // Don't rewrite the button text if the actual text doesn't change. - // This causes unnecessary and confusing information for screen reader users. - // This check is needed because this function gets called every time the volume level is changed. - if(this.player_.muted()){ - if(this.el_.children[0].children[0].innerHTML!=this.localize('Unmute')){ - this.el_.children[0].children[0].innerHTML = this.localize('Unmute'); // change the button text to "Unmute" - } - } else { - if(this.el_.children[0].children[0].innerHTML!=this.localize('Mute')){ - this.el_.children[0].children[0].innerHTML = this.localize('Mute'); // change the button text to "Mute" - } - } + /** + * Update volume + * + * @method update + */ - /* TODO improve muted icon classes */ - for (var i = 0; i < 4; i++) { - vjs.removeClass(this.el_, 'vjs-vol-'+i); - } - vjs.addClass(this.el_, 'vjs-vol-'+level); -}; -/** - * Menu button with a popup for showing the volume slider. - * @constructor - */ -vjs.VolumeMenuButton = vjs.MenuButton.extend({ - /** @constructor */ - init: function(player, options){ - vjs.MenuButton.call(this, player, options); + MuteToggle.prototype.update = function update() { + var vol = this.player_.volume(), + level = 3; - // Same listeners as MuteToggle - this.on(player, 'volumechange', this.volumeUpdate); + if (vol === 0 || this.player_.muted()) { + level = 0; + } else if (vol < 0.33) { + level = 1; + } else if (vol < 0.67) { + level = 2; + } - // hide mute toggle if the current tech doesn't support volume control - if (player.tech && player.tech['featuresVolumeControl'] === false) { - this.addClass('vjs-hidden'); + // Don't rewrite the button text if the actual text doesn't change. + // This causes unnecessary and confusing information for screen reader users. + // This check is needed because this function gets called every time the volume level is changed. + var toMute = this.player_.muted() ? 'Unmute' : 'Mute'; + if (this.controlText() !== toMute) { + this.controlText(toMute); } - this.on(player, 'loadstart', function(){ - if (player.tech['featuresVolumeControl'] === false) { - this.addClass('vjs-hidden'); - } else { - this.removeClass('vjs-hidden'); - } - }); - this.addClass('vjs-menu-button'); - } -}); -vjs.VolumeMenuButton.prototype.createMenu = function(){ - var menu = new vjs.Menu(this.player_, { - contentElType: 'div' - }); - var vc = new vjs.VolumeBar(this.player_, this.options_['volumeBar']); - vc.on('focus', function() { - menu.lockShowing(); - }); - vc.on('blur', function() { - menu.unlockShowing(); - }); - menu.addChild(vc); - return menu; -}; + /* TODO improve muted icon classes */ + for (var i = 0; i < 4; i++) { + Dom.removeElClass(this.el_, 'vjs-vol-' + i); + } + Dom.addElClass(this.el_, 'vjs-vol-' + level); + }; -vjs.VolumeMenuButton.prototype.onClick = function(){ - vjs.MuteToggle.prototype.onClick.call(this); - vjs.MenuButton.prototype.onClick.call(this); -}; + return MuteToggle; +})(_button2['default']); -vjs.VolumeMenuButton.prototype.createEl = function(){ - return vjs.Button.prototype.createEl.call(this, 'div', { - className: 'vjs-volume-menu-button vjs-menu-button vjs-control', - innerHTML: '
    ' + this.localize('Mute') + '
    ' - }); -}; -vjs.VolumeMenuButton.prototype.volumeUpdate = vjs.MuteToggle.prototype.update; +MuteToggle.prototype.controlText_ = 'Mute'; + +_component2['default'].registerComponent('MuteToggle', MuteToggle); +exports['default'] = MuteToggle; +module.exports = exports['default']; + +},{"../button":64,"../component":67,"../utils/dom.js":142}],74:[function(_dereq_,module,exports){ /** - * The component for controlling the playback rate - * - * @param {vjs.Player|Object} player - * @param {Object=} options - * @constructor + * @file play-toggle.js */ -vjs.PlaybackRateMenuButton = vjs.MenuButton.extend({ - /** @constructor */ - init: function(player, options){ - vjs.MenuButton.call(this, player, options); +'use strict'; - this.updateVisibility(); - this.updateLabel(); +exports.__esModule = true; - this.on(player, 'loadstart', this.updateVisibility); - this.on(player, 'ratechange', this.updateLabel); - } -}); +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } -vjs.PlaybackRateMenuButton.prototype.buttonText = 'Playback Rate'; -vjs.PlaybackRateMenuButton.prototype.className = 'vjs-playback-rate'; +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } -vjs.PlaybackRateMenuButton.prototype.createEl = function(){ - var el = vjs.MenuButton.prototype.createEl.call(this); +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - this.labelEl_ = vjs.createEl('div', { - className: 'vjs-playback-rate-value', - innerHTML: 1.0 - }); +var _buttonJs = _dereq_('../button.js'); - el.appendChild(this.labelEl_); +var _buttonJs2 = _interopRequireDefault(_buttonJs); - return el; -}; +var _componentJs = _dereq_('../component.js'); -// Menu creation -vjs.PlaybackRateMenuButton.prototype.createMenu = function(){ - var menu = new vjs.Menu(this.player()); - var rates = this.player().options()['playbackRates']; +var _componentJs2 = _interopRequireDefault(_componentJs); - if (rates) { - for (var i = rates.length - 1; i >= 0; i--) { - menu.addChild( - new vjs.PlaybackRateMenuItem(this.player(), { 'rate': rates[i] + 'x'}) - ); - } - } +/** + * Button to toggle between play and pause + * + * @param {Player|Object} player + * @param {Object=} options + * @extends Button + * @class PlayToggle + */ - return menu; -}; +var PlayToggle = (function (_Button) { + _inherits(PlayToggle, _Button); -vjs.PlaybackRateMenuButton.prototype.updateARIAAttributes = function(){ - // Current playback rate - this.el().setAttribute('aria-valuenow', this.player().playbackRate()); -}; + function PlayToggle(player, options) { + _classCallCheck(this, PlayToggle); -vjs.PlaybackRateMenuButton.prototype.onClick = function(){ - // select next rate option - var currentRate = this.player().playbackRate(); - var rates = this.player().options()['playbackRates']; - // this will select first one if the last one currently selected - var newRate = rates[0]; - for (var i = 0; i currentRate) { - newRate = rates[i]; - break; - } + _Button.call(this, player, options); + + this.on(player, 'play', this.handlePlay); + this.on(player, 'pause', this.handlePause); } - this.player().playbackRate(newRate); -}; -vjs.PlaybackRateMenuButton.prototype.playbackRateSupported = function(){ - return this.player().tech - && this.player().tech['featuresPlaybackRate'] - && this.player().options()['playbackRates'] - && this.player().options()['playbackRates'].length > 0 - ; -}; + /** + * Allow sub components to stack CSS class names + * + * @return {String} The constructed class name + * @method buildCSSClass + */ -/** - * Hide playback rate controls when they're no playback rate options to select - */ -vjs.PlaybackRateMenuButton.prototype.updateVisibility = function(){ - if (this.playbackRateSupported()) { - this.removeClass('vjs-hidden'); - } else { - this.addClass('vjs-hidden'); - } -}; + PlayToggle.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-play-control ' + _Button.prototype.buildCSSClass.call(this); + }; -/** - * Update button label when rate changed - */ -vjs.PlaybackRateMenuButton.prototype.updateLabel = function(){ - if (this.playbackRateSupported()) { - this.labelEl_.innerHTML = this.player().playbackRate() + 'x'; - } -}; + /** + * Handle click to toggle between play and pause + * + * @method handleClick + */ -/** - * The specific menu item type for selecting a playback rate - * - * @constructor - */ -vjs.PlaybackRateMenuItem = vjs.MenuItem.extend({ - contentElType: 'button', - /** @constructor */ - init: function(player, options){ - var label = this.label = options['rate']; - var rate = this.rate = parseFloat(label, 10); + PlayToggle.prototype.handleClick = function handleClick() { + if (this.player_.paused()) { + this.player_.play(); + } else { + this.player_.pause(); + } + }; - // Modify options for parent MenuItem class's init. - options['label'] = label; - options['selected'] = rate === 1; - vjs.MenuItem.call(this, player, options); + /** + * Add the vjs-playing class to the element so it can change appearance + * + * @method handlePlay + */ - this.on(player, 'ratechange', this.update); - } -}); + PlayToggle.prototype.handlePlay = function handlePlay() { + this.removeClass('vjs-paused'); + this.addClass('vjs-playing'); + this.controlText('Pause'); // change the button text to "Pause" + }; -vjs.PlaybackRateMenuItem.prototype.onClick = function(){ - vjs.MenuItem.prototype.onClick.call(this); - this.player().playbackRate(this.rate); -}; + /** + * Add the vjs-paused class to the element so it can change appearance + * + * @method handlePause + */ -vjs.PlaybackRateMenuItem.prototype.update = function(){ - this.selected(this.player().playbackRate() == this.rate); -}; -/* Poster Image -================================================================================ */ -/** - * The component that handles showing the poster image. - * - * @param {vjs.Player|Object} player - * @param {Object=} options - * @constructor - */ -vjs.PosterImage = vjs.Button.extend({ - /** @constructor */ - init: function(player, options){ - vjs.Button.call(this, player, options); + PlayToggle.prototype.handlePause = function handlePause() { + this.removeClass('vjs-playing'); + this.addClass('vjs-paused'); + this.controlText('Play'); // change the button text to "Play" + }; - this.update(); - player.on('posterchange', vjs.bind(this, this.update)); - } -}); + return PlayToggle; +})(_buttonJs2['default']); -/** - * Clean up the poster image - */ -vjs.PosterImage.prototype.dispose = function(){ - this.player().off('posterchange', this.update); - vjs.Button.prototype.dispose.call(this); -}; +PlayToggle.prototype.controlText_ = 'Play'; + +_componentJs2['default'].registerComponent('PlayToggle', PlayToggle); +exports['default'] = PlayToggle; +module.exports = exports['default']; +},{"../button.js":64,"../component.js":67}],75:[function(_dereq_,module,exports){ /** - * Create the poster image element - * @return {Element} + * @file playback-rate-menu-button.js */ -vjs.PosterImage.prototype.createEl = function(){ - var el = vjs.createEl('div', { - className: 'vjs-poster', - - // Don't want poster to be tabbable. - tabIndex: -1 - }); +'use strict'; - // To ensure the poster image resizes while maintaining its original aspect - // ratio, use a div with `background-size` when available. For browsers that - // do not support `background-size` (e.g. IE8), fall back on using a regular - // img element. - if (!vjs.BACKGROUND_SIZE_SUPPORTED) { - this.fallbackImg_ = vjs.createEl('img'); - el.appendChild(this.fallbackImg_); - } +exports.__esModule = true; - return el; -}; +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } -/** - * Event handler for updates to the player's poster source - */ -vjs.PosterImage.prototype.update = function(){ - var url = this.player().poster(); +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - this.setSrc(url); +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - // If there's no poster source we should display:none on this component - // so it's not still clickable or right-clickable - if (url) { - this.show(); - } else { - this.hide(); - } -}; +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } -/** - * Set the poster source depending on the display method - */ -vjs.PosterImage.prototype.setSrc = function(url){ - var backgroundImage; +var _menuMenuButtonJs = _dereq_('../../menu/menu-button.js'); - if (this.fallbackImg_) { - this.fallbackImg_.src = url; - } else { - backgroundImage = ''; - // Any falsey values should stay as an empty string, otherwise - // this will throw an extra error - if (url) { - backgroundImage = 'url("' + url + '")'; - } +var _menuMenuButtonJs2 = _interopRequireDefault(_menuMenuButtonJs); - this.el_.style.backgroundImage = backgroundImage; - } -}; +var _menuMenuJs = _dereq_('../../menu/menu.js'); -/** - * Event handler for clicks on the poster image - */ -vjs.PosterImage.prototype.onClick = function(){ - // We don't want a click to trigger playback when controls are disabled - // but CSS should be hiding the poster to prevent that from happening - this.player_.play(); -}; -/* Loading Spinner -================================================================================ */ -/** - * Loading spinner for waiting events - * @param {vjs.Player|Object} player - * @param {Object=} options - * @class - * @constructor - */ -vjs.LoadingSpinner = vjs.Component.extend({ - /** @constructor */ - init: function(player, options){ - vjs.Component.call(this, player, options); +var _menuMenuJs2 = _interopRequireDefault(_menuMenuJs); - // MOVING DISPLAY HANDLING TO CSS +var _playbackRateMenuItemJs = _dereq_('./playback-rate-menu-item.js'); - // player.on('canplay', vjs.bind(this, this.hide)); - // player.on('canplaythrough', vjs.bind(this, this.hide)); - // player.on('playing', vjs.bind(this, this.hide)); - // player.on('seeking', vjs.bind(this, this.show)); +var _playbackRateMenuItemJs2 = _interopRequireDefault(_playbackRateMenuItemJs); - // in some browsers seeking does not trigger the 'playing' event, - // so we also need to trap 'seeked' if we are going to set a - // 'seeking' event - // player.on('seeked', vjs.bind(this, this.hide)); +var _componentJs = _dereq_('../../component.js'); - // player.on('ended', vjs.bind(this, this.hide)); +var _componentJs2 = _interopRequireDefault(_componentJs); - // Not showing spinner on stalled any more. Browsers may stall and then not trigger any events that would remove the spinner. - // Checked in Chrome 16 and Safari 5.1.2. http://help.videojs.com/discussions/problems/883-why-is-the-download-progress-showing - // player.on('stalled', vjs.bind(this, this.show)); +var _utilsDomJs = _dereq_('../../utils/dom.js'); - // player.on('waiting', vjs.bind(this, this.show)); - } -}); +var Dom = _interopRequireWildcard(_utilsDomJs); -vjs.LoadingSpinner.prototype.createEl = function(){ - return vjs.Component.prototype.createEl.call(this, 'div', { - className: 'vjs-loading-spinner' - }); -}; -/* Big Play Button -================================================================================ */ /** - * Initial play button. Shows before the video has played. The hiding of the - * big play button is done via CSS and player states. - * @param {vjs.Player|Object} player + * The component for controlling the playback rate + * + * @param {Player|Object} player * @param {Object=} options - * @class - * @constructor + * @extends MenuButton + * @class PlaybackRateMenuButton */ -vjs.BigPlayButton = vjs.Button.extend(); -vjs.BigPlayButton.prototype.createEl = function(){ - return vjs.Button.prototype.createEl.call(this, 'div', { - className: 'vjs-big-play-button', - innerHTML: '', - 'aria-label': 'play video' - }); -}; +var PlaybackRateMenuButton = (function (_MenuButton) { + _inherits(PlaybackRateMenuButton, _MenuButton); -vjs.BigPlayButton.prototype.onClick = function(){ - this.player_.play(); -}; -/** - * Display that an error has occurred making the video unplayable - * @param {vjs.Player|Object} player - * @param {Object=} options - * @constructor - */ -vjs.ErrorDisplay = vjs.Component.extend({ - init: function(player, options){ - vjs.Component.call(this, player, options); + function PlaybackRateMenuButton(player, options) { + _classCallCheck(this, PlaybackRateMenuButton); - this.update(); - this.on(player, 'error', this.update); + _MenuButton.call(this, player, options); + + this.updateVisibility(); + this.updateLabel(); + + this.on(player, 'loadstart', this.updateVisibility); + this.on(player, 'ratechange', this.updateLabel); } -}); -vjs.ErrorDisplay.prototype.createEl = function(){ - var el = vjs.Component.prototype.createEl.call(this, 'div', { - className: 'vjs-error-display' - }); + /** + * Create the component's DOM element + * + * @return {Element} + * @method createEl + */ - this.contentEl_ = vjs.createEl('div'); - el.appendChild(this.contentEl_); + PlaybackRateMenuButton.prototype.createEl = function createEl() { + var el = _MenuButton.prototype.createEl.call(this); - return el; -}; + this.labelEl_ = Dom.createEl('div', { + className: 'vjs-playback-rate-value', + innerHTML: 1.0 + }); -vjs.ErrorDisplay.prototype.update = function(){ - if (this.player().error()) { - this.contentEl_.innerHTML = this.localize(this.player().error().message); - } -}; -(function() { - var createTrackHelper; -/** - * @fileoverview Media Technology Controller - Base class for media playback - * technology controllers like Flash and HTML5 - */ + el.appendChild(this.labelEl_); -/** - * Base class for media (HTML5 Video, Flash) controllers - * @param {vjs.Player|Object} player Central player instance - * @param {Object=} options Options object - * @constructor - */ -vjs.MediaTechController = vjs.Component.extend({ - /** @constructor */ - init: function(player, options, ready){ - options = options || {}; - // we don't want the tech to report user activity automatically. - // This is done manually in addControlsListeners - options.reportTouchActivity = false; - vjs.Component.call(this, player, options, ready); + return el; + }; - // Manually track progress in cases where the browser/flash player doesn't report it. - if (!this['featuresProgressEvents']) { - this.manualProgressOn(); - } + /** + * Allow sub components to stack CSS class names + * + * @return {String} The constructed class name + * @method buildCSSClass + */ - // Manually track timeupdates in cases where the browser/flash player doesn't report it. - if (!this['featuresTimeupdateEvents']) { - this.manualTimeUpdatesOn(); - } + PlaybackRateMenuButton.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-playback-rate ' + _MenuButton.prototype.buildCSSClass.call(this); + }; + + /** + * Create the playback rate menu + * + * @return {Menu} Menu object populated with items + * @method createMenu + */ - this.initControlsListeners(); + PlaybackRateMenuButton.prototype.createMenu = function createMenu() { + var menu = new _menuMenuJs2['default'](this.player()); + var rates = this.playbackRates(); - if (!this['featuresNativeTextTracks']) { - this.emulateTextTracks(); + if (rates) { + for (var i = rates.length - 1; i >= 0; i--) { + menu.addChild(new _playbackRateMenuItemJs2['default'](this.player(), { 'rate': rates[i] + 'x' })); + } } - this.initTextTrackListeners(); - } -}); + return menu; + }; -/** - * Set up click and touch listeners for the playback element - * On desktops, a click on the video itself will toggle playback, - * on a mobile device a click on the video toggles controls. - * (toggling controls is done by toggling the user state between active and - * inactive) - * - * A tap can signal that a user has become active, or has become inactive - * e.g. a quick tap on an iPhone movie should reveal the controls. Another - * quick tap should hide them again (signaling the user is in an inactive - * viewing state) - * - * In addition to this, we still want the user to be considered inactive after - * a few seconds of inactivity. - * - * Note: the only part of iOS interaction we can't mimic with this setup - * is a touch and hold on the video element counting as activity in order to - * keep the controls showing, but that shouldn't be an issue. A touch and hold on - * any controls will still keep the user active - */ -vjs.MediaTechController.prototype.initControlsListeners = function(){ - var player, activateControls; + /** + * Updates ARIA accessibility attributes + * + * @method updateARIAAttributes + */ + + PlaybackRateMenuButton.prototype.updateARIAAttributes = function updateARIAAttributes() { + // Current playback rate + this.el().setAttribute('aria-valuenow', this.player().playbackRate()); + }; + + /** + * Handle menu item click + * + * @method handleClick + */ - player = this.player(); + PlaybackRateMenuButton.prototype.handleClick = function handleClick() { + // select next rate option + var currentRate = this.player().playbackRate(); + var rates = this.playbackRates(); - activateControls = function(){ - if (player.controls() && !player.usingNativeControls()) { - this.addControlsListeners(); + // this will select first one if the last one currently selected + var newRate = rates[0]; + for (var i = 0; i < rates.length; i++) { + if (rates[i] > currentRate) { + newRate = rates[i]; + break; + } } + this.player().playbackRate(newRate); }; - // Set up event listeners once the tech is ready and has an element to apply - // listeners to - this.ready(activateControls); - this.on(player, 'controlsenabled', activateControls); - this.on(player, 'controlsdisabled', this.removeControlsListeners); + /** + * Get possible playback rates + * + * @return {Array} Possible playback rates + * @method playbackRates + */ - // if we're loading the playback object after it has started loading or playing the - // video (often with autoplay on) then the loadstart event has already fired and we - // need to fire it manually because many things rely on it. - // Long term we might consider how we would do this for other events like 'canplay' - // that may also have fired. - this.ready(function(){ - if (this.networkState && this.networkState() > 0) { - this.player().trigger('loadstart'); - } - }); -}; + PlaybackRateMenuButton.prototype.playbackRates = function playbackRates() { + return this.options_['playbackRates'] || this.options_.playerOptions && this.options_.playerOptions['playbackRates']; + }; -vjs.MediaTechController.prototype.addControlsListeners = function(){ - var userWasActive; + /** + * Get whether playback rates is supported by the tech + * and an array of playback rates exists + * + * @return {Boolean} Whether changing playback rate is supported + * @method playbackRateSupported + */ - // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do - // trigger mousedown/up. - // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object - // Any touch events are set to block the mousedown event from happening - this.on('mousedown', this.onClick); + PlaybackRateMenuButton.prototype.playbackRateSupported = function playbackRateSupported() { + return this.player().tech_ && this.player().tech_['featuresPlaybackRate'] && this.playbackRates() && this.playbackRates().length > 0; + }; - // If the controls were hidden we don't want that to change without a tap event - // so we'll check if the controls were already showing before reporting user - // activity - this.on('touchstart', function(event) { - userWasActive = this.player_.userActive(); - }); + /** + * Hide playback rate controls when they're no playback rate options to select + * + * @method updateVisibility + */ - this.on('touchmove', function(event) { - if (userWasActive){ - this.player().reportUserActivity(); + PlaybackRateMenuButton.prototype.updateVisibility = function updateVisibility() { + if (this.playbackRateSupported()) { + this.removeClass('vjs-hidden'); + } else { + this.addClass('vjs-hidden'); } - }); + }; - this.on('touchend', function(event) { - // Stop the mouse events from also happening - event.preventDefault(); - }); + /** + * Update button label when rate changed + * + * @method updateLabel + */ + + PlaybackRateMenuButton.prototype.updateLabel = function updateLabel() { + if (this.playbackRateSupported()) { + this.labelEl_.innerHTML = this.player().playbackRate() + 'x'; + } + }; - // Turn on component tap events - this.emitTapEvents(); + return PlaybackRateMenuButton; +})(_menuMenuButtonJs2['default']); - // The tap listener needs to come after the touchend listener because the tap - // listener cancels out any reportedUserActivity when setting userActive(false) - this.on('tap', this.onTap); -}; +PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate'; -/** - * Remove the listeners used for click and tap controls. This is needed for - * toggling to controls disabled, where a tap/touch should do nothing. - */ -vjs.MediaTechController.prototype.removeControlsListeners = function(){ - // We don't want to just use `this.off()` because there might be other needed - // listeners added by techs that extend this. - this.off('tap'); - this.off('touchstart'); - this.off('touchmove'); - this.off('touchleave'); - this.off('touchcancel'); - this.off('touchend'); - this.off('click'); - this.off('mousedown'); -}; +_componentJs2['default'].registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton); +exports['default'] = PlaybackRateMenuButton; +module.exports = exports['default']; +},{"../../component.js":67,"../../menu/menu-button.js":109,"../../menu/menu.js":111,"../../utils/dom.js":142,"./playback-rate-menu-item.js":76}],76:[function(_dereq_,module,exports){ /** - * Handle a click on the media element. By default will play/pause the media. + * @file playback-rate-menu-item.js */ -vjs.MediaTechController.prototype.onClick = function(event){ - // We're using mousedown to detect clicks thanks to Flash, but mousedown - // will also be triggered with right-clicks, so we need to prevent that - if (event.button !== 0) return; +'use strict'; - // When controls are disabled a click should not toggle playback because - // the click is considered a control - if (this.player().controls()) { - if (this.player().paused()) { - this.player().play(); - } else { - this.player().pause(); - } - } -}; +exports.__esModule = true; -/** - * Handle a tap on the media element. By default it will toggle the user - * activity state, which hides and shows the controls. - */ -vjs.MediaTechController.prototype.onTap = function(){ - this.player().userActive(!this.player().userActive()); -}; +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } -/* Fallbacks for unsupported event types -================================================================================ */ -// Manually trigger progress events based on changes to the buffered amount -// Many flash players and older HTML5 browsers don't send progress or progress-like events -vjs.MediaTechController.prototype.manualProgressOn = function(){ - this.manualProgress = true; +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - // Trigger progress watching when a source begins loading - this.trackProgress(); -}; +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } -vjs.MediaTechController.prototype.manualProgressOff = function(){ - this.manualProgress = false; - this.stopTrackingProgress(); -}; +var _menuMenuItemJs = _dereq_('../../menu/menu-item.js'); -vjs.MediaTechController.prototype.trackProgress = function(){ - this.progressInterval = this.setInterval(function(){ - // Don't trigger unless buffered amount is greater than last time +var _menuMenuItemJs2 = _interopRequireDefault(_menuMenuItemJs); - var bufferedPercent = this.player().bufferedPercent(); +var _componentJs = _dereq_('../../component.js'); - if (this.bufferedPercent_ != bufferedPercent) { - this.player().trigger('progress'); - } +var _componentJs2 = _interopRequireDefault(_componentJs); - this.bufferedPercent_ = bufferedPercent; +/** + * The specific menu item type for selecting a playback rate + * + * @param {Player|Object} player + * @param {Object=} options + * @extends MenuItem + * @class PlaybackRateMenuItem + */ - if (bufferedPercent === 1) { - this.stopTrackingProgress(); - } - }, 500); -}; -vjs.MediaTechController.prototype.stopTrackingProgress = function(){ this.clearInterval(this.progressInterval); }; +var PlaybackRateMenuItem = (function (_MenuItem) { + _inherits(PlaybackRateMenuItem, _MenuItem); -/*! Time Tracking -------------------------------------------------------------- */ -vjs.MediaTechController.prototype.manualTimeUpdatesOn = function(){ - var player = this.player_; + function PlaybackRateMenuItem(player, options) { + _classCallCheck(this, PlaybackRateMenuItem); - this.manualTimeUpdates = true; + var label = options['rate']; + var rate = parseFloat(label, 10); - this.on(player, 'play', this.trackCurrentTime); - this.on(player, 'pause', this.stopTrackingCurrentTime); - // timeupdate is also called by .currentTime whenever current time is set + // Modify options for parent MenuItem class's init. + options['label'] = label; + options['selected'] = rate === 1; + _MenuItem.call(this, player, options); - // Watch for native timeupdate event - this.one('timeupdate', function(){ - // Update known progress support for this playback technology - this['featuresTimeupdateEvents'] = true; - // Turn off manual progress tracking - this.manualTimeUpdatesOff(); - }); -}; + this.label = label; + this.rate = rate; -vjs.MediaTechController.prototype.manualTimeUpdatesOff = function(){ - var player = this.player_; + this.on(player, 'ratechange', this.update); + } - this.manualTimeUpdates = false; - this.stopTrackingCurrentTime(); - this.off(player, 'play', this.trackCurrentTime); - this.off(player, 'pause', this.stopTrackingCurrentTime); -}; + /** + * Handle click on menu item + * + * @method handleClick + */ -vjs.MediaTechController.prototype.trackCurrentTime = function(){ - if (this.currentTimeInterval) { this.stopTrackingCurrentTime(); } - this.currentTimeInterval = this.setInterval(function(){ - this.player().trigger('timeupdate'); - }, 250); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 -}; + PlaybackRateMenuItem.prototype.handleClick = function handleClick() { + _MenuItem.prototype.handleClick.call(this); + this.player().playbackRate(this.rate); + }; -// Turn off play progress tracking (when paused or dragging) -vjs.MediaTechController.prototype.stopTrackingCurrentTime = function(){ - this.clearInterval(this.currentTimeInterval); + /** + * Update playback rate with selected rate + * + * @method update + */ - // #1002 - if the video ends right before the next timeupdate would happen, - // the progress bar won't make it all the way to the end - this.player().trigger('timeupdate'); -}; + PlaybackRateMenuItem.prototype.update = function update() { + this.selected(this.player().playbackRate() === this.rate); + }; -vjs.MediaTechController.prototype.dispose = function() { - // Turn off any manual progress or timeupdate tracking - if (this.manualProgress) { this.manualProgressOff(); } + return PlaybackRateMenuItem; +})(_menuMenuItemJs2['default']); - if (this.manualTimeUpdates) { this.manualTimeUpdatesOff(); } +PlaybackRateMenuItem.prototype.contentElType = 'button'; - vjs.Component.prototype.dispose.call(this); -}; +_componentJs2['default'].registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem); +exports['default'] = PlaybackRateMenuItem; +module.exports = exports['default']; -vjs.MediaTechController.prototype.setCurrentTime = function() { - // improve the accuracy of manual timeupdates - if (this.manualTimeUpdates) { this.player().trigger('timeupdate'); } -}; +},{"../../component.js":67,"../../menu/menu-item.js":110}],77:[function(_dereq_,module,exports){ +/** + * @file load-progress-bar.js + */ +'use strict'; -// TODO: Consider looking at moving this into the text track display directly -// https://github.com/videojs/video.js/issues/1863 -vjs.MediaTechController.prototype.initTextTrackListeners = function() { - var player = this.player_, - tracks, - textTrackListChanges = function() { - var textTrackDisplay = player.getChild('textTrackDisplay'), - controlBar; +exports.__esModule = true; - if (textTrackDisplay) { - textTrackDisplay.updateDisplay(); - } - }; +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - tracks = this.textTracks(); +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - if (!tracks) { - return; - } +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - tracks.addEventListener('removetrack', textTrackListChanges); - tracks.addEventListener('addtrack', textTrackListChanges); +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - this.on('dispose', vjs.bind(this, function() { - tracks.removeEventListener('removetrack', textTrackListChanges); - tracks.removeEventListener('addtrack', textTrackListChanges); - })); -}; +var _componentJs = _dereq_('../../component.js'); -vjs.MediaTechController.prototype.emulateTextTracks = function() { - var player = this.player_, - textTracksChanges, - tracks, - script; +var _componentJs2 = _interopRequireDefault(_componentJs); - if (!window['WebVTT']) { - script = document.createElement('script'); - script.src = player.options()['vtt.js'] || '../node_modules/vtt.js/dist/vtt.js'; - player.el().appendChild(script); - window['WebVTT'] = true; - } +var _utilsDomJs = _dereq_('../../utils/dom.js'); - tracks = this.textTracks(); - if (!tracks) { - return; +var Dom = _interopRequireWildcard(_utilsDomJs); + +/** + * Shows load progress + * + * @param {Player|Object} player + * @param {Object=} options + * @extends Component + * @class LoadProgressBar + */ + +var LoadProgressBar = (function (_Component) { + _inherits(LoadProgressBar, _Component); + + function LoadProgressBar(player, options) { + _classCallCheck(this, LoadProgressBar); + + _Component.call(this, player, options); + this.on(player, 'progress', this.update); } - textTracksChanges = function() { - var i, track, textTrackDisplay; + /** + * Create the component's DOM element + * + * @return {Element} + * @method createEl + */ + + LoadProgressBar.prototype.createEl = function createEl() { + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-load-progress', + innerHTML: '' + this.localize('Loaded') + ': 0%' + }); + }; + + /** + * Update progress bar + * + * @method update + */ + + LoadProgressBar.prototype.update = function update() { + var buffered = this.player_.buffered(); + var duration = this.player_.duration(); + var bufferedEnd = this.player_.bufferedEnd(); + var children = this.el_.children; + + // get the percent width of a time compared to the total end + var percentify = function percentify(time, end) { + var percent = time / end || 0; // no NaN + return (percent >= 1 ? 1 : percent) * 100 + '%'; + }; - textTrackDisplay = player.getChild('textTrackDisplay'), + // update the width of the progress bar + this.el_.style.width = percentify(bufferedEnd, duration); - textTrackDisplay.updateDisplay(); + // add child elements to represent the individual buffered time ranges + for (var i = 0; i < buffered.length; i++) { + var start = buffered.start(i); + var end = buffered.end(i); + var part = children[i]; - for (i = 0; i < this.length; i++) { - track = this[i]; - track.removeEventListener('cuechange', vjs.bind(textTrackDisplay, textTrackDisplay.updateDisplay)); - if (track.mode === 'showing') { - track.addEventListener('cuechange', vjs.bind(textTrackDisplay, textTrackDisplay.updateDisplay)); + if (!part) { + part = this.el_.appendChild(Dom.createEl()); } + + // set the percent based on the width of the progress bar (bufferedEnd) + part.style.left = percentify(start, bufferedEnd); + part.style.width = percentify(end - start, bufferedEnd); + } + + // remove unused buffered range elements + for (var i = children.length; i > buffered.length; i--) { + this.el_.removeChild(children[i - 1]); } }; - tracks.addEventListener('change', textTracksChanges); + return LoadProgressBar; +})(_componentJs2['default']); - this.on('dispose', vjs.bind(this, function() { - tracks.removeEventListener('change', textTracksChanges); - })); -}; +_componentJs2['default'].registerComponent('LoadProgressBar', LoadProgressBar); +exports['default'] = LoadProgressBar; +module.exports = exports['default']; +},{"../../component.js":67,"../../utils/dom.js":142}],78:[function(_dereq_,module,exports){ /** - * Provide default methods for text tracks. - * - * Html5 tech overrides these. + * @file mouse-time-display.js */ +'use strict'; -/** - * List of associated text tracks - * @type {Array} - * @private - */ -vjs.MediaTechController.prototype.textTracks_; +exports.__esModule = true; -vjs.MediaTechController.prototype.textTracks = function() { - this.player_.textTracks_ = this.player_.textTracks_ || new vjs.TextTrackList(); - return this.player_.textTracks_; -}; +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } -vjs.MediaTechController.prototype.remoteTextTracks = function() { - this.player_.remoteTextTracks_ = this.player_.remoteTextTracks_ || new vjs.TextTrackList(); - return this.player_.remoteTextTracks_; -}; +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } -createTrackHelper = function(self, kind, label, language, options) { - var tracks = self.textTracks(), - track; +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - options = options || {}; +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - options['kind'] = kind; - if (label) { - options['label'] = label; - } - if (language) { - options['language'] = language; - } - options['player'] = self.player_; +var _globalWindow = _dereq_('global/window'); - track = new vjs.TextTrack(options); - tracks.addTrack_(track); +var _globalWindow2 = _interopRequireDefault(_globalWindow); - return track; -}; +var _componentJs = _dereq_('../../component.js'); -vjs.MediaTechController.prototype.addTextTrack = function(kind, label, language) { - if (!kind) { - throw new Error('TextTrack kind is required but was not provided'); - } +var _componentJs2 = _interopRequireDefault(_componentJs); - return createTrackHelper(this, kind, label, language); -}; +var _utilsDomJs = _dereq_('../../utils/dom.js'); -vjs.MediaTechController.prototype.addRemoteTextTrack = function(options) { - var track = createTrackHelper(this, options['kind'], options['label'], options['language'], options); - this.remoteTextTracks().addTrack_(track); - return { - track: track - }; -}; +var Dom = _interopRequireWildcard(_utilsDomJs); -vjs.MediaTechController.prototype.removeRemoteTextTrack = function(track) { - this.textTracks().removeTrack_(track); - this.remoteTextTracks().removeTrack_(track); -}; +var _utilsFnJs = _dereq_('../../utils/fn.js'); -/** - * Provide a default setPoster method for techs - * - * Poster support for techs should be optional, so we don't want techs to - * break if they don't have a way to set a poster. - */ -vjs.MediaTechController.prototype.setPoster = function(){}; +var Fn = _interopRequireWildcard(_utilsFnJs); -vjs.MediaTechController.prototype['featuresVolumeControl'] = true; +var _utilsFormatTimeJs = _dereq_('../../utils/format-time.js'); -// Resizing plugins using request fullscreen reloads the plugin -vjs.MediaTechController.prototype['featuresFullscreenResize'] = false; -vjs.MediaTechController.prototype['featuresPlaybackRate'] = false; +var _utilsFormatTimeJs2 = _interopRequireDefault(_utilsFormatTimeJs); -// Optional events that we can manually mimic with timers -// currently not triggered by video-js-swf -vjs.MediaTechController.prototype['featuresProgressEvents'] = false; -vjs.MediaTechController.prototype['featuresTimeupdateEvents'] = false; +var _lodashCompatFunctionThrottle = _dereq_('lodash-compat/function/throttle'); -vjs.MediaTechController.prototype['featuresNativeTextTracks'] = false; +var _lodashCompatFunctionThrottle2 = _interopRequireDefault(_lodashCompatFunctionThrottle); /** - * A functional mixin for techs that want to use the Source Handler pattern. - * - * ##### EXAMPLE: - * - * videojs.MediaTechController.withSourceHandlers.call(MyTech); + * The Mouse Time Display component shows the time you will seek to + * when hovering over the progress bar * + * @param {Player|Object} player + * @param {Object=} options + * @extends Component + * @class MouseTimeDisplay */ -vjs.MediaTechController.withSourceHandlers = function(Tech){ - /** - * Register a source handler - * Source handlers are scripts for handling specific formats. - * The source handler pattern is used for adaptive formats (HLS, DASH) that - * manually load video data and feed it into a Source Buffer (Media Source Extensions) - * @param {Function} handler The source handler - * @param {Boolean} first Register it before any existing handlers - */ - Tech['registerSourceHandler'] = function(handler, index){ - var handlers = Tech.sourceHandlers; - if (!handlers) { - handlers = Tech.sourceHandlers = []; +var MouseTimeDisplay = (function (_Component) { + _inherits(MouseTimeDisplay, _Component); + + function MouseTimeDisplay(player, options) { + var _this = this; + + _classCallCheck(this, MouseTimeDisplay); + + _Component.call(this, player, options); + + if (options.playerOptions && options.playerOptions.controlBar && options.playerOptions.controlBar.progressControl && options.playerOptions.controlBar.progressControl.keepTooltipsInside) { + this.keepTooltipsInside = options.playerOptions.controlBar.progressControl.keepTooltipsInside; } - if (index === undefined) { - // add to the end of the list - index = handlers.length; + if (this.keepTooltipsInside) { + this.tooltip = Dom.createEl('div', { className: 'vjs-time-tooltip' }); + this.el().appendChild(this.tooltip); + this.addClass('vjs-keep-tooltips-inside'); } - handlers.splice(index, 0, handler); - }; + this.update(0, 0); + + player.on('ready', function () { + _this.on(player.controlBar.progressControl.el(), 'mousemove', _lodashCompatFunctionThrottle2['default'](Fn.bind(_this, _this.handleMouseMove), 25)); + }); + } /** - * Return the first source handler that supports the source - * TODO: Answer question: should 'probably' be prioritized over 'maybe' - * @param {Object} source The source object - * @returns {Object} The first source handler that supports the source - * @returns {null} Null if no source handler is found + * Create the component's DOM element + * + * @return {Element} + * @method createEl */ - Tech.selectSourceHandler = function(source){ - var handlers = Tech.sourceHandlers || [], - can; - for (var i = 0; i < handlers.length; i++) { - can = handlers[i]['canHandleSource'](source); + MouseTimeDisplay.prototype.createEl = function createEl() { + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-mouse-display' + }); + }; - if (can) { - return handlers[i]; - } - } + MouseTimeDisplay.prototype.handleMouseMove = function handleMouseMove(event) { + var duration = this.player_.duration(); + var newTime = this.calculateDistance(event) * duration; + var position = event.pageX - Dom.findElPosition(this.el().parentNode).left; - return null; + this.update(newTime, position); }; - /** - * Check if the tech can support the given source - * @param {Object} srcObj The source object - * @return {String} 'probably', 'maybe', or '' (empty string) - */ - Tech.canPlaySource = function(srcObj){ - var sh = Tech.selectSourceHandler(srcObj); + MouseTimeDisplay.prototype.update = function update(newTime, position) { + var time = _utilsFormatTimeJs2['default'](newTime, this.player_.duration()); - if (sh) { - return sh['canHandleSource'](srcObj); + this.el().style.left = position + 'px'; + this.el().setAttribute('data-current-time', time); + + if (this.keepTooltipsInside) { + var clampedPosition = this.clampPosition_(position); + var difference = position - clampedPosition + 1; + var tooltipWidth = parseFloat(_globalWindow2['default'].getComputedStyle(this.tooltip).width); + var tooltipWidthHalf = tooltipWidth / 2; + + this.tooltip.innerHTML = time; + this.tooltip.style.right = '-' + (tooltipWidthHalf - difference) + 'px'; } + }; - return ''; + MouseTimeDisplay.prototype.calculateDistance = function calculateDistance(event) { + return Dom.getPointerPosition(this.el().parentNode, event).x; }; /** - * Create a function for setting the source using a source object - * and source handlers. - * Should never be called unless a source handler was found. - * @param {Object} source A source object with src and type keys - * @return {vjs.MediaTechController} self + * This takes in a horizontal position for the bar and returns a clamped position. + * Clamped position means that it will keep the position greater than half the width + * of the tooltip and smaller than the player width minus half the width o the tooltip. + * It will only clamp the position if `keepTooltipsInside` option is set. + * + * @param {Number} position the position the bar wants to be + * @return {Number} newPosition the (potentially) clamped position + * @method clampPosition_ */ - Tech.prototype.setSource = function(source){ - var sh = Tech.selectSourceHandler(source); - if (!sh) { - // Fall back to a native source hander when unsupported sources are - // deliberately set - if (Tech['nativeSourceHandler']) { - sh = Tech['nativeSourceHandler']; - } else { - vjs.log.error('No source hander found for the current source.'); - } + MouseTimeDisplay.prototype.clampPosition_ = function clampPosition_(position) { + if (!this.keepTooltipsInside) { + return position; } - // Dispose any existing source handler - this.disposeSourceHandler(); - this.off('dispose', this.disposeSourceHandler); - - this.currentSource_ = source; - this.sourceHandler_ = sh['handleSource'](source, this); - this.on('dispose', this.disposeSourceHandler); - - return this; - }; + var playerWidth = parseFloat(_globalWindow2['default'].getComputedStyle(this.player().el()).width); + var tooltipWidth = parseFloat(_globalWindow2['default'].getComputedStyle(this.tooltip).width); + var tooltipWidthHalf = tooltipWidth / 2; + var actualPosition = position; - /** - * Clean up any existing source handler - */ - Tech.prototype.disposeSourceHandler = function(){ - if (this.sourceHandler_ && this.sourceHandler_['dispose']) { - this.sourceHandler_['dispose'](); + if (position < tooltipWidthHalf) { + actualPosition = Math.ceil(tooltipWidthHalf); + } else if (position > playerWidth - tooltipWidthHalf) { + actualPosition = Math.floor(playerWidth - tooltipWidthHalf); } + + return actualPosition; }; -}; + return MouseTimeDisplay; +})(_componentJs2['default']); -vjs.media = {}; +_componentJs2['default'].registerComponent('MouseTimeDisplay', MouseTimeDisplay); +exports['default'] = MouseTimeDisplay; +module.exports = exports['default']; -})(); +},{"../../component.js":67,"../../utils/dom.js":142,"../../utils/fn.js":144,"../../utils/format-time.js":145,"global/window":8,"lodash-compat/function/throttle":13}],79:[function(_dereq_,module,exports){ /** - * @fileoverview HTML5 Media Controller - Wrapper for HTML5 Media API + * @file play-progress-bar.js */ +'use strict'; -/** - * HTML5 Media Controller - Wrapper for HTML5 Media API - * @param {vjs.Player|Object} player - * @param {Object=} options - * @param {Function=} ready - * @constructor - */ -vjs.Html5 = vjs.MediaTechController.extend({ - /** @constructor */ - init: function(player, options, ready){ - var nodes, nodesLength, i, node, nodeName, removeNodes; +exports.__esModule = true; - if (options['nativeCaptions'] === false || options['nativeTextTracks'] === false) { - this['featuresNativeTextTracks'] = false; - } +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - vjs.MediaTechController.call(this, player, options, ready); +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - this.setupTriggers(); +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var source = options['source']; +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - // Set the source if one is provided - // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted) - // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source - // anyway so the error gets fired. - if (source && (this.el_.currentSrc !== source.src || (player.tag && player.tag.initNetworkState_ === 3))) { - this.setSource(source); - } +var _componentJs = _dereq_('../../component.js'); - if (this.el_.hasChildNodes()) { +var _componentJs2 = _interopRequireDefault(_componentJs); - nodes = this.el_.childNodes; - nodesLength = nodes.length; - removeNodes = []; +var _utilsFnJs = _dereq_('../../utils/fn.js'); - while (nodesLength--) { - node = nodes[nodesLength]; - nodeName = node.nodeName.toLowerCase(); - if (nodeName === 'track') { - if (!this['featuresNativeTextTracks']) { - // Empty video tag tracks so the built-in player doesn't use them also. - // This may not be fast enough to stop HTML5 browsers from reading the tags - // so we'll need to turn off any default tracks if we're manually doing - // captions and subtitles. videoElement.textTracks - removeNodes.push(node); - } else { - this.remoteTextTracks().addTrack_(node['track']); - } - } - } +var Fn = _interopRequireWildcard(_utilsFnJs); - for (i=0; i
  • ` + * + * @param {Player|Object} player + * @param {Object=} options + * @extends Button + * @class MenuItem + */ + +var MenuItem = (function (_ClickableComponent) { + _inherits(MenuItem, _ClickableComponent); + + function MenuItem(player, options) { + _classCallCheck(this, MenuItem); + + _ClickableComponent.call(this, player, options); + + this.selectable = options['selectable']; + + this.selected(options['selected']); + + if (this.selectable) { + // TODO: May need to be either menuitemcheckbox or menuitemradio, + // and may need logical grouping of menu items. + this.el_.setAttribute('role', 'menuitemcheckbox'); + } else { + this.el_.setAttribute('role', 'menuitem'); + } + } + + /** + * Create the component's DOM element + * + * @param {String=} type Desc + * @param {Object=} props Desc + * @return {Element} + * @method createEl + */ + + MenuItem.prototype.createEl = function createEl(type, props, attrs) { + return _ClickableComponent.prototype.createEl.call(this, 'li', _objectAssign2['default']({ + className: 'vjs-menu-item', + innerHTML: this.localize(this.options_['label']), + tabIndex: -1 + }, props), attrs); + }; + + /** + * Handle a click on the menu item, and set it to selected + * + * @method handleClick + */ + + MenuItem.prototype.handleClick = function handleClick() { + this.selected(true); + }; + + /** + * Set this menu item as selected or not + * + * @param {Boolean} selected + * @method selected + */ + + MenuItem.prototype.selected = function selected(_selected) { + if (this.selectable) { + if (_selected) { + this.addClass('vjs-selected'); + this.el_.setAttribute('aria-checked', 'true'); + // aria-checked isn't fully supported by browsers/screen readers, + // so indicate selected state to screen reader in the control text. + this.controlText(', selected'); + } else { + this.removeClass('vjs-selected'); + this.el_.setAttribute('aria-checked', 'false'); + // Indicate un-selected state to screen reader + // Note that a space clears out the selected state text + this.controlText(' '); + } + } + }; + + return MenuItem; +})(_clickableComponentJs2['default']); + +_componentJs2['default'].registerComponent('MenuItem', MenuItem); +exports['default'] = MenuItem; +module.exports = exports['default']; + +},{"../clickable-component.js":65,"../component.js":67,"object.assign":53}],111:[function(_dereq_,module,exports){ +/** + * @file menu.js + */ +'use strict'; + +exports.__esModule = true; + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _componentJs = _dereq_('../component.js'); + +var _componentJs2 = _interopRequireDefault(_componentJs); + +var _utilsDomJs = _dereq_('../utils/dom.js'); + +var Dom = _interopRequireWildcard(_utilsDomJs); + +var _utilsFnJs = _dereq_('../utils/fn.js'); + +var Fn = _interopRequireWildcard(_utilsFnJs); + +var _utilsEventsJs = _dereq_('../utils/events.js'); + +var Events = _interopRequireWildcard(_utilsEventsJs); + +/** + * The Menu component is used to build pop up menus, including subtitle and + * captions selection menus. + * + * @extends Component + * @class Menu + */ + +var Menu = (function (_Component) { + _inherits(Menu, _Component); + + function Menu(player, options) { + _classCallCheck(this, Menu); + + _Component.call(this, player, options); + + this.focusedChild_ = -1; + + this.on('keydown', this.handleKeyPress); + } + + /** + * Add a menu item to the menu + * + * @param {Object|String} component Component or component type to add + * @method addItem + */ + + Menu.prototype.addItem = function addItem(component) { + this.addChild(component); + component.on('click', Fn.bind(this, function () { + this.unlockShowing(); + //TODO: Need to set keyboard focus back to the menuButton + })); + }; + + /** + * Create the component's DOM element + * + * @return {Element} + * @method createEl + */ + + Menu.prototype.createEl = function createEl() { + var contentElType = this.options_.contentElType || 'ul'; + this.contentEl_ = Dom.createEl(contentElType, { + className: 'vjs-menu-content' + }); + this.contentEl_.setAttribute('role', 'menu'); + var el = _Component.prototype.createEl.call(this, 'div', { + append: this.contentEl_, + className: 'vjs-menu' + }); + el.setAttribute('role', 'presentation'); + el.appendChild(this.contentEl_); + + // Prevent clicks from bubbling up. Needed for Menu Buttons, + // where a click on the parent is significant + Events.on(el, 'click', function (event) { + event.preventDefault(); + event.stopImmediatePropagation(); + }); + + return el; + }; + + /** + * Handle key press for menu + * + * @param {Object} event Event object + * @method handleKeyPress + */ + + Menu.prototype.handleKeyPress = function handleKeyPress(event) { + if (event.which === 37 || event.which === 40) { + // Left and Down Arrows + event.preventDefault(); + this.stepForward(); + } else if (event.which === 38 || event.which === 39) { + // Up and Right Arrows + event.preventDefault(); + this.stepBack(); + } + }; + + /** + * Move to next (lower) menu item for keyboard users + * + * @method stepForward + */ + + Menu.prototype.stepForward = function stepForward() { + var stepChild = 0; + + if (this.focusedChild_ !== undefined) { + stepChild = this.focusedChild_ + 1; + } + this.focus(stepChild); + }; + + /** + * Move to previous (higher) menu item for keyboard users + * + * @method stepBack + */ + + Menu.prototype.stepBack = function stepBack() { + var stepChild = 0; + + if (this.focusedChild_ !== undefined) { + stepChild = this.focusedChild_ - 1; + } + this.focus(stepChild); + }; + + /** + * Set focus on a menu item in the menu + * + * @param {Object|String} item Index of child item set focus on + * @method focus + */ + + Menu.prototype.focus = function focus() { + var item = arguments.length <= 0 || arguments[0] === undefined ? 0 : arguments[0]; + + var children = this.children().slice(); + var haveTitle = children.length && children[0].className && /vjs-menu-title/.test(children[0].className); + + if (haveTitle) { + children.shift(); + } + + if (children.length > 0) { + if (item < 0) { + item = 0; + } else if (item >= children.length) { + item = children.length - 1; + } + + this.focusedChild_ = item; + + children[item].el_.focus(); + } + }; + + return Menu; +})(_componentJs2['default']); + +_componentJs2['default'].registerComponent('Menu', Menu); +exports['default'] = Menu; +module.exports = exports['default']; + +},{"../component.js":67,"../utils/dom.js":142,"../utils/events.js":143,"../utils/fn.js":144}],112:[function(_dereq_,module,exports){ +/** + * @file modal-dialog.js + */ +'use strict'; + +exports.__esModule = true; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _utilsDom = _dereq_('./utils/dom'); + +var Dom = _interopRequireWildcard(_utilsDom); + +var _utilsFn = _dereq_('./utils/fn'); + +var Fn = _interopRequireWildcard(_utilsFn); + +var _utilsLog = _dereq_('./utils/log'); + +var _utilsLog2 = _interopRequireDefault(_utilsLog); + +var _component = _dereq_('./component'); + +var _component2 = _interopRequireDefault(_component); + +var _closeButton = _dereq_('./close-button'); + +var _closeButton2 = _interopRequireDefault(_closeButton); + +var MODAL_CLASS_NAME = 'vjs-modal-dialog'; +var ESC = 27; + +/** + * The `ModalDialog` displays over the video and its controls, which blocks + * interaction with the player until it is closed. + * + * Modal dialogs include a "Close" button and will close when that button + * is activated - or when ESC is pressed anywhere. + * + * @extends Component + * @class ModalDialog + */ + +var ModalDialog = (function (_Component) { + _inherits(ModalDialog, _Component); + + /** + * Constructor for modals. + * + * @param {Player} player + * @param {Object} [options] + * @param {Mixed} [options.content=undefined] + * Provide customized content for this modal. + * + * @param {String} [options.description] + * A text description for the modal, primarily for accessibility. + * + * @param {Boolean} [options.fillAlways=false] + * Normally, modals are automatically filled only the first time + * they open. This tells the modal to refresh its content + * every time it opens. + * + * @param {String} [options.label] + * A text label for the modal, primarily for accessibility. + * + * @param {Boolean} [options.temporary=true] + * If `true`, the modal can only be opened once; it will be + * disposed as soon as it's closed. + * + * @param {Boolean} [options.uncloseable=false] + * If `true`, the user will not be able to close the modal + * through the UI in the normal ways. Programmatic closing is + * still possible. + * + */ + + function ModalDialog(player, options) { + _classCallCheck(this, ModalDialog); + + _Component.call(this, player, options); + this.opened_ = this.hasBeenOpened_ = this.hasBeenFilled_ = false; + + this.closeable(!this.options_.uncloseable); + this.content(this.options_.content); + + // Make sure the contentEl is defined AFTER any children are initialized + // because we only want the contents of the modal in the contentEl + // (not the UI elements like the close button). + this.contentEl_ = Dom.createEl('div', { + className: MODAL_CLASS_NAME + '-content' + }, { + role: 'document' + }); + + this.descEl_ = Dom.createEl('p', { + className: MODAL_CLASS_NAME + '-description vjs-offscreen', + id: this.el().getAttribute('aria-describedby') + }); + + Dom.textContent(this.descEl_, this.description()); + this.el_.appendChild(this.descEl_); + this.el_.appendChild(this.contentEl_); + } + + /* + * Modal dialog default options. + * + * @type {Object} + * @private + */ + + /** + * Create the modal's DOM element + * + * @method createEl + * @return {Element} + */ + + ModalDialog.prototype.createEl = function createEl() { + return _Component.prototype.createEl.call(this, 'div', { + className: this.buildCSSClass(), + tabIndex: -1 + }, { + 'aria-describedby': this.id() + '_description', + 'aria-hidden': 'true', + 'aria-label': this.label(), + role: 'dialog' + }); + }; + + /** + * Build the modal's CSS class. + * + * @method buildCSSClass + * @return {String} + */ + + ModalDialog.prototype.buildCSSClass = function buildCSSClass() { + return MODAL_CLASS_NAME + ' vjs-hidden ' + _Component.prototype.buildCSSClass.call(this); + }; + + /** + * Handles key presses on the document, looking for ESC, which closes + * the modal. + * + * @method handleKeyPress + * @param {Event} e + */ + + ModalDialog.prototype.handleKeyPress = function handleKeyPress(e) { + if (e.which === ESC && this.closeable()) { + this.close(); + } + }; + + /** + * Returns the label string for this modal. Primarily used for accessibility. + * + * @return {String} + */ + + ModalDialog.prototype.label = function label() { + return this.options_.label || this.localize('Modal Window'); + }; + + /** + * Returns the description string for this modal. Primarily used for + * accessibility. + * + * @return {String} + */ + + ModalDialog.prototype.description = function description() { + var desc = this.options_.description || this.localize('This is a modal window.'); + + // Append a universal closeability message if the modal is closeable. + if (this.closeable()) { + desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.'); + } + + return desc; + }; + + /** + * Opens the modal. + * + * @method open + * @return {ModalDialog} + */ + + ModalDialog.prototype.open = function open() { + if (!this.opened_) { + var player = this.player(); + + this.trigger('beforemodalopen'); + this.opened_ = true; + + // Fill content if the modal has never opened before and + // never been filled. + if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) { + this.fill(); + } + + // If the player was playing, pause it and take note of its previously + // playing state. + this.wasPlaying_ = !player.paused(); + + if (this.wasPlaying_) { + player.pause(); + } + + if (this.closeable()) { + this.on(this.el_.ownerDocument, 'keydown', Fn.bind(this, this.handleKeyPress)); + } + + player.controls(false); + this.show(); + this.el().setAttribute('aria-hidden', 'false'); + this.trigger('modalopen'); + this.hasBeenOpened_ = true; + } + return this; + }; + + /** + * Whether or not the modal is opened currently. + * + * @method opened + * @param {Boolean} [value] + * If given, it will open (`true`) or close (`false`) the modal. + * + * @return {Boolean} + */ + + ModalDialog.prototype.opened = function opened(value) { + if (typeof value === 'boolean') { + this[value ? 'open' : 'close'](); + } + return this.opened_; + }; + + /** + * Closes the modal. + * + * @method close + * @return {ModalDialog} + */ + + ModalDialog.prototype.close = function close() { + if (this.opened_) { + var player = this.player(); + + this.trigger('beforemodalclose'); + this.opened_ = false; + + if (this.wasPlaying_) { + player.play(); + } + + if (this.closeable()) { + this.off(this.el_.ownerDocument, 'keydown', Fn.bind(this, this.handleKeyPress)); + } + + player.controls(true); + this.hide(); + this.el().setAttribute('aria-hidden', 'true'); + this.trigger('modalclose'); + + if (this.options_.temporary) { + this.dispose(); + } + } + return this; + }; + + /** + * Whether or not the modal is closeable via the UI. + * + * @method closeable + * @param {Boolean} [value] + * If given as a Boolean, it will set the `closeable` option. + * + * @return {Boolean} + */ + + ModalDialog.prototype.closeable = function closeable(value) { + if (typeof value === 'boolean') { + var closeable = this.closeable_ = !!value; + var _close = this.getChild('closeButton'); + + // If this is being made closeable and has no close button, add one. + if (closeable && !_close) { + + // The close button should be a child of the modal - not its + // content element, so temporarily change the content element. + var temp = this.contentEl_; + this.contentEl_ = this.el_; + _close = this.addChild('closeButton'); + this.contentEl_ = temp; + this.on(_close, 'close', this.close); + } + + // If this is being made uncloseable and has a close button, remove it. + if (!closeable && _close) { + this.off(_close, 'close', this.close); + this.removeChild(_close); + _close.dispose(); + } + } + return this.closeable_; + }; + + /** + * Fill the modal's content element with the modal's "content" option. + * + * The content element will be emptied before this change takes place. + * + * @method fill + * @return {ModalDialog} + */ + + ModalDialog.prototype.fill = function fill() { + return this.fillWith(this.content()); + }; + + /** + * Fill the modal's content element with arbitrary content. + * + * The content element will be emptied before this change takes place. + * + * @method fillWith + * @param {Mixed} [content] + * The same rules apply to this as apply to the `content` option. + * + * @return {ModalDialog} + */ + + ModalDialog.prototype.fillWith = function fillWith(content) { + var contentEl = this.contentEl(); + var parentEl = contentEl.parentNode; + var nextSiblingEl = contentEl.nextSibling; + + this.trigger('beforemodalfill'); + this.hasBeenFilled_ = true; + + // Detach the content element from the DOM before performing + // manipulation to avoid modifying the live DOM multiple times. + parentEl.removeChild(contentEl); + this.empty(); + Dom.insertContent(contentEl, content); + this.trigger('modalfill'); + + // Re-inject the re-filled content element. + if (nextSiblingEl) { + parentEl.insertBefore(contentEl, nextSiblingEl); + } else { + parentEl.appendChild(contentEl); + } + + return this; + }; + + /** + * Empties the content element. + * + * This happens automatically anytime the modal is filled. + * + * @method empty + * @return {ModalDialog} + */ + + ModalDialog.prototype.empty = function empty() { + this.trigger('beforemodalempty'); + Dom.emptyEl(this.contentEl()); + this.trigger('modalempty'); + return this; + }; + + /** + * Gets or sets the modal content, which gets normalized before being + * rendered into the DOM. + * + * This does not update the DOM or fill the modal, but it is called during + * that process. + * + * @method content + * @param {Mixed} [value] + * If defined, sets the internal content value to be used on the + * next call(s) to `fill`. This value is normalized before being + * inserted. To "clear" the internal content value, pass `null`. + * + * @return {Mixed} + */ + + ModalDialog.prototype.content = function content(value) { + if (typeof value !== 'undefined') { + this.content_ = value; + } + return this.content_; + }; + + return ModalDialog; +})(_component2['default']); + +ModalDialog.prototype.options_ = { + temporary: true +}; + +_component2['default'].registerComponent('ModalDialog', ModalDialog); +exports['default'] = ModalDialog; +module.exports = exports['default']; + +},{"./close-button":66,"./component":67,"./utils/dom":142,"./utils/fn":144,"./utils/log":147}],113:[function(_dereq_,module,exports){ +/** + * @file player.js + */ +// Subclasses Component +'use strict'; + +exports.__esModule = true; + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _componentJs = _dereq_('./component.js'); + +var _componentJs2 = _interopRequireDefault(_componentJs); + +var _globalDocument = _dereq_('global/document'); + +var _globalDocument2 = _interopRequireDefault(_globalDocument); + +var _globalWindow = _dereq_('global/window'); + +var _globalWindow2 = _interopRequireDefault(_globalWindow); + +var _utilsEventsJs = _dereq_('./utils/events.js'); + +var Events = _interopRequireWildcard(_utilsEventsJs); + +var _utilsDomJs = _dereq_('./utils/dom.js'); + +var Dom = _interopRequireWildcard(_utilsDomJs); + +var _utilsFnJs = _dereq_('./utils/fn.js'); + +var Fn = _interopRequireWildcard(_utilsFnJs); + +var _utilsGuidJs = _dereq_('./utils/guid.js'); + +var Guid = _interopRequireWildcard(_utilsGuidJs); + +var _utilsBrowserJs = _dereq_('./utils/browser.js'); + +var browser = _interopRequireWildcard(_utilsBrowserJs); + +var _utilsLogJs = _dereq_('./utils/log.js'); + +var _utilsLogJs2 = _interopRequireDefault(_utilsLogJs); + +var _utilsToTitleCaseJs = _dereq_('./utils/to-title-case.js'); + +var _utilsToTitleCaseJs2 = _interopRequireDefault(_utilsToTitleCaseJs); + +var _utilsTimeRangesJs = _dereq_('./utils/time-ranges.js'); + +var _utilsBufferJs = _dereq_('./utils/buffer.js'); + +var _utilsStylesheetJs = _dereq_('./utils/stylesheet.js'); + +var stylesheet = _interopRequireWildcard(_utilsStylesheetJs); + +var _fullscreenApiJs = _dereq_('./fullscreen-api.js'); + +var _fullscreenApiJs2 = _interopRequireDefault(_fullscreenApiJs); + +var _mediaErrorJs = _dereq_('./media-error.js'); + +var _mediaErrorJs2 = _interopRequireDefault(_mediaErrorJs); + +var _safeJsonParseTuple = _dereq_('safe-json-parse/tuple'); + +var _safeJsonParseTuple2 = _interopRequireDefault(_safeJsonParseTuple); + +var _objectAssign = _dereq_('object.assign'); + +var _objectAssign2 = _interopRequireDefault(_objectAssign); + +var _utilsMergeOptionsJs = _dereq_('./utils/merge-options.js'); + +var _utilsMergeOptionsJs2 = _interopRequireDefault(_utilsMergeOptionsJs); + +var _tracksTextTrackListConverterJs = _dereq_('./tracks/text-track-list-converter.js'); + +var _tracksTextTrackListConverterJs2 = _interopRequireDefault(_tracksTextTrackListConverterJs); + +var _tracksAudioTrackListJs = _dereq_('./tracks/audio-track-list.js'); + +var _tracksAudioTrackListJs2 = _interopRequireDefault(_tracksAudioTrackListJs); + +var _tracksVideoTrackListJs = _dereq_('./tracks/video-track-list.js'); + +var _tracksVideoTrackListJs2 = _interopRequireDefault(_tracksVideoTrackListJs); + +// Include required child components (importing also registers them) + +var _techLoaderJs = _dereq_('./tech/loader.js'); + +var _techLoaderJs2 = _interopRequireDefault(_techLoaderJs); + +var _posterImageJs = _dereq_('./poster-image.js'); + +var _posterImageJs2 = _interopRequireDefault(_posterImageJs); + +var _tracksTextTrackDisplayJs = _dereq_('./tracks/text-track-display.js'); + +var _tracksTextTrackDisplayJs2 = _interopRequireDefault(_tracksTextTrackDisplayJs); + +var _loadingSpinnerJs = _dereq_('./loading-spinner.js'); + +var _loadingSpinnerJs2 = _interopRequireDefault(_loadingSpinnerJs); + +var _bigPlayButtonJs = _dereq_('./big-play-button.js'); + +var _bigPlayButtonJs2 = _interopRequireDefault(_bigPlayButtonJs); + +var _controlBarControlBarJs = _dereq_('./control-bar/control-bar.js'); + +var _controlBarControlBarJs2 = _interopRequireDefault(_controlBarControlBarJs); + +var _errorDisplayJs = _dereq_('./error-display.js'); + +var _errorDisplayJs2 = _interopRequireDefault(_errorDisplayJs); + +var _tracksTextTrackSettingsJs = _dereq_('./tracks/text-track-settings.js'); + +var _tracksTextTrackSettingsJs2 = _interopRequireDefault(_tracksTextTrackSettingsJs); + +var _modalDialog = _dereq_('./modal-dialog'); + +var _modalDialog2 = _interopRequireDefault(_modalDialog); + +// Require html5 tech, at least for disposing the original video tag + +var _techTechJs = _dereq_('./tech/tech.js'); + +var _techTechJs2 = _interopRequireDefault(_techTechJs); + +var _techHtml5Js = _dereq_('./tech/html5.js'); + +var _techHtml5Js2 = _interopRequireDefault(_techHtml5Js); + +/** + * An instance of the `Player` class is created when any of the Video.js setup methods are used to initialize a video. + * ```js + * var myPlayer = videojs('example_video_1'); + * ``` + * In the following example, the `data-setup` attribute tells the Video.js library to create a player instance when the library is ready. + * ```html + * + * ``` + * After an instance has been created it can be accessed globally using `Video('example_video_1')`. + * + * @param {Element} tag The original video tag used for configuring options + * @param {Object=} options Object of option names and values + * @param {Function=} ready Ready callback function + * @extends Component + * @class Player + */ + +var Player = (function (_Component) { + _inherits(Player, _Component); + + /** + * player's constructor function + * + * @constructs + * @method init + * @param {Element} tag The original video tag used for configuring options + * @param {Object=} options Player options + * @param {Function=} ready Ready callback function + */ + + function Player(tag, options, ready) { + var _this = this; + + _classCallCheck(this, Player); + + // Make sure tag ID exists + tag.id = tag.id || 'vjs_video_' + Guid.newGUID(); + + // Set Options + // The options argument overrides options set in the video tag + // which overrides globally set options. + // This latter part coincides with the load order + // (tag must exist before Player) + options = _objectAssign2['default'](Player.getTagSettings(tag), options); + + // Delay the initialization of children because we need to set up + // player properties first, and can't use `this` before `super()` + options.initChildren = false; + + // Same with creating the element + options.createEl = false; + + // we don't want the player to report touch activity on itself + // see enableTouchActivity in Component + options.reportTouchActivity = false; + + // Run base component initializing with new options + _Component.call(this, null, options, ready); + + // if the global option object was accidentally blown away by + // someone, bail early with an informative error + if (!this.options_ || !this.options_.techOrder || !this.options_.techOrder.length) { + throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?'); + } + + this.tag = tag; // Store the original tag used to set options + + // Store the tag attributes used to restore html5 element + this.tagAttributes = tag && Dom.getElAttributes(tag); + + // Update current language + this.language(this.options_.language); + + // Update Supported Languages + if (options.languages) { + (function () { + // Normalise player option languages to lowercase + var languagesToLower = {}; + + Object.getOwnPropertyNames(options.languages).forEach(function (name) { + languagesToLower[name.toLowerCase()] = options.languages[name]; + }); + _this.languages_ = languagesToLower; + })(); + } else { + this.languages_ = Player.prototype.options_.languages; + } + + // Cache for video property values. + this.cache_ = {}; + + // Set poster + this.poster_ = options.poster || ''; + + // Set controls + this.controls_ = !!options.controls; + + // Original tag settings stored in options + // now remove immediately so native controls don't flash. + // May be turned back on by HTML5 tech if nativeControlsForTouch is true + tag.controls = false; + + /* + * Store the internal state of scrubbing + * + * @private + * @return {Boolean} True if the user is scrubbing + */ + this.scrubbing_ = false; + + this.el_ = this.createEl(); + + // We also want to pass the original player options to each component and plugin + // as well so they don't need to reach back into the player for options later. + // We also need to do another copy of this.options_ so we don't end up with + // an infinite loop. + var playerOptionsCopy = _utilsMergeOptionsJs2['default'](this.options_); + + // Load plugins + if (options.plugins) { + (function () { + var plugins = options.plugins; + + Object.getOwnPropertyNames(plugins).forEach(function (name) { + if (typeof this[name] === 'function') { + this[name](plugins[name]); + } else { + _utilsLogJs2['default'].error('Unable to find plugin:', name); + } + }, _this); + })(); + } + + this.options_.playerOptions = playerOptionsCopy; + + this.initChildren(); + + // Set isAudio based on whether or not an audio tag was used + this.isAudio(tag.nodeName.toLowerCase() === 'audio'); + + // Update controls className. Can't do this when the controls are initially + // set because the element doesn't exist yet. + if (this.controls()) { + this.addClass('vjs-controls-enabled'); + } else { + this.addClass('vjs-controls-disabled'); + } + + // Set ARIA label and region role depending on player type + this.el_.setAttribute('role', 'region'); + if (this.isAudio()) { + this.el_.setAttribute('aria-label', 'audio player'); + } else { + this.el_.setAttribute('aria-label', 'video player'); + } + + if (this.isAudio()) { + this.addClass('vjs-audio'); + } + + if (this.flexNotSupported_()) { + this.addClass('vjs-no-flex'); + } + + // TODO: Make this smarter. Toggle user state between touching/mousing + // using events, since devices can have both touch and mouse events. + // if (browser.TOUCH_ENABLED) { + // this.addClass('vjs-touch-enabled'); + // } + + // iOS Safari has broken hover handling + if (!browser.IS_IOS) { + this.addClass('vjs-workinghover'); + } + + // Make player easily findable by ID + Player.players[this.id_] = this; + + // When the player is first initialized, trigger activity so components + // like the control bar show themselves if needed + this.userActive(true); + this.reportUserActivity(); + this.listenForUserActivity_(); + + this.on('fullscreenchange', this.handleFullscreenChange_); + this.on('stageclick', this.handleStageClick_); + } + + /* + * Global player list + * + * @type {Object} + */ + + /** + * Destroys the video player and does any necessary cleanup + * ```js + * myPlayer.dispose(); + * ``` + * This is especially helpful if you are dynamically adding and removing videos + * to/from the DOM. + * + * @method dispose + */ + + Player.prototype.dispose = function dispose() { + this.trigger('dispose'); + // prevent dispose from being called twice + this.off('dispose'); + + if (this.styleEl_ && this.styleEl_.parentNode) { + this.styleEl_.parentNode.removeChild(this.styleEl_); + } + + // Kill reference to this player + Player.players[this.id_] = null; + if (this.tag && this.tag.player) { + this.tag.player = null; + } + if (this.el_ && this.el_.player) { + this.el_.player = null; + } + + if (this.tech_) { + this.tech_.dispose(); + } + + _Component.prototype.dispose.call(this); + }; + + /** + * Create the component's DOM element + * + * @return {Element} + * @method createEl + */ + + Player.prototype.createEl = function createEl() { + var el = this.el_ = _Component.prototype.createEl.call(this, 'div'); + var tag = this.tag; + + // Remove width/height attrs from tag so CSS can make it 100% width/height + tag.removeAttribute('width'); + tag.removeAttribute('height'); + + // Copy over all the attributes from the tag, including ID and class + // ID will now reference player box, not the video tag + var attrs = Dom.getElAttributes(tag); + + Object.getOwnPropertyNames(attrs).forEach(function (attr) { + // workaround so we don't totally break IE7 + // http://stackoverflow.com/questions/3653444/css-styles-not-applied-on-dynamic-elements-in-internet-explorer-7 + if (attr === 'class') { + el.className = attrs[attr]; + } else { + el.setAttribute(attr, attrs[attr]); + } + }); + + // Update tag id/class for use as HTML5 playback tech + // Might think we should do this after embedding in container so .vjs-tech class + // doesn't flash 100% width/height, but class only applies with .video-js parent + tag.playerId = tag.id; + tag.id += '_html5_api'; + tag.className = 'vjs-tech'; + + // Make player findable on elements + tag.player = el.player = this; + // Default state of video is paused + this.addClass('vjs-paused'); + + // Add a style element in the player that we'll use to set the width/height + // of the player in a way that's still overrideable by CSS, just like the + // video element + if (_globalWindow2['default'].VIDEOJS_NO_DYNAMIC_STYLE !== true) { + this.styleEl_ = stylesheet.createStyleElement('vjs-styles-dimensions'); + var defaultsStyleEl = Dom.$('.vjs-styles-defaults'); + var head = Dom.$('head'); + head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild); + } + + // Pass in the width/height/aspectRatio options which will update the style el + this.width(this.options_.width); + this.height(this.options_.height); + this.fluid(this.options_.fluid); + this.aspectRatio(this.options_.aspectRatio); + + // Hide any links within the video/audio tag, because IE doesn't hide them completely. + var links = tag.getElementsByTagName('a'); + for (var i = 0; i < links.length; i++) { + var linkEl = links.item(i); + Dom.addElClass(linkEl, 'vjs-hidden'); + linkEl.setAttribute('hidden', 'hidden'); + } + + // insertElFirst seems to cause the networkState to flicker from 3 to 2, so + // keep track of the original for later so we can know if the source originally failed + tag.initNetworkState_ = tag.networkState; + + // Wrap video tag in div (el/box) container + if (tag.parentNode) { + tag.parentNode.insertBefore(el, tag); + } + + // insert the tag as the first child of the player element + // then manually add it to the children array so that this.addChild + // will work properly for other components + Dom.insertElFirst(tag, el); // Breaks iPhone, fixed in HTML5 setup. + this.children_.unshift(tag); + + this.el_ = el; + + return el; + }; + + /** + * Get/set player width + * + * @param {Number=} value Value for width + * @return {Number} Width when getting + * @method width + */ + + Player.prototype.width = function width(value) { + return this.dimension('width', value); + }; + + /** + * Get/set player height + * + * @param {Number=} value Value for height + * @return {Number} Height when getting + * @method height + */ + + Player.prototype.height = function height(value) { + return this.dimension('height', value); + }; + + /** + * Get/set dimension for player + * + * @param {String} dimension Either width or height + * @param {Number=} value Value for dimension + * @return {Component} + * @method dimension + */ + + Player.prototype.dimension = function dimension(_dimension, value) { + var privDimension = _dimension + '_'; + + if (value === undefined) { + return this[privDimension] || 0; + } + + if (value === '') { + // If an empty string is given, reset the dimension to be automatic + this[privDimension] = undefined; + } else { + var parsedVal = parseFloat(value); + + if (isNaN(parsedVal)) { + _utilsLogJs2['default'].error('Improper value "' + value + '" supplied for for ' + _dimension); + return this; + } + + this[privDimension] = parsedVal; + } + + this.updateStyleEl_(); + return this; + }; + + /** + * Add/remove the vjs-fluid class + * + * @param {Boolean} bool Value of true adds the class, value of false removes the class + * @method fluid + */ + + Player.prototype.fluid = function fluid(bool) { + if (bool === undefined) { + return !!this.fluid_; + } + + this.fluid_ = !!bool; + + if (bool) { + this.addClass('vjs-fluid'); + } else { + this.removeClass('vjs-fluid'); + } + }; + + /** + * Get/Set the aspect ratio + * + * @param {String=} ratio Aspect ratio for player + * @return aspectRatio + * @method aspectRatio + */ + + Player.prototype.aspectRatio = function aspectRatio(ratio) { + if (ratio === undefined) { + return this.aspectRatio_; + } + + // Check for width:height format + if (!/^\d+\:\d+$/.test(ratio)) { + throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.'); + } + this.aspectRatio_ = ratio; + + // We're assuming if you set an aspect ratio you want fluid mode, + // because in fixed mode you could calculate width and height yourself. + this.fluid(true); + + this.updateStyleEl_(); + }; + + /** + * Update styles of the player element (height, width and aspect ratio) + * + * @method updateStyleEl_ + */ + + Player.prototype.updateStyleEl_ = function updateStyleEl_() { + if (_globalWindow2['default'].VIDEOJS_NO_DYNAMIC_STYLE === true) { + var _width = typeof this.width_ === 'number' ? this.width_ : this.options_.width; + var _height = typeof this.height_ === 'number' ? this.height_ : this.options_.height; + var techEl = this.tech_ && this.tech_.el(); + + if (techEl) { + if (_width >= 0) { + techEl.width = _width; + } + if (_height >= 0) { + techEl.height = _height; + } + } + + return; + } + + var width = undefined; + var height = undefined; + var aspectRatio = undefined; + var idClass = undefined; + + // The aspect ratio is either used directly or to calculate width and height. + if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') { + // Use any aspectRatio that's been specifically set + aspectRatio = this.aspectRatio_; + } else if (this.videoWidth()) { + // Otherwise try to get the aspect ratio from the video metadata + aspectRatio = this.videoWidth() + ':' + this.videoHeight(); + } else { + // Or use a default. The video element's is 2:1, but 16:9 is more common. + aspectRatio = '16:9'; + } + + // Get the ratio as a decimal we can use to calculate dimensions + var ratioParts = aspectRatio.split(':'); + var ratioMultiplier = ratioParts[1] / ratioParts[0]; + + if (this.width_ !== undefined) { + // Use any width that's been specifically set + width = this.width_; + } else if (this.height_ !== undefined) { + // Or calulate the width from the aspect ratio if a height has been set + width = this.height_ / ratioMultiplier; + } else { + // Or use the video's metadata, or use the video el's default of 300 + width = this.videoWidth() || 300; + } + + if (this.height_ !== undefined) { + // Use any height that's been specifically set + height = this.height_; + } else { + // Otherwise calculate the height from the ratio and the width + height = width * ratioMultiplier; + } + + // Ensure the CSS class is valid by starting with an alpha character + if (/^[^a-zA-Z]/.test(this.id())) { + idClass = 'dimensions-' + this.id(); + } else { + idClass = this.id() + '-dimensions'; + } + + // Ensure the right class is still on the player for the style element + this.addClass(idClass); + + stylesheet.setTextContent(this.styleEl_, '\n .' + idClass + ' {\n width: ' + width + 'px;\n height: ' + height + 'px;\n }\n\n .' + idClass + '.vjs-fluid {\n padding-top: ' + ratioMultiplier * 100 + '%;\n }\n '); + }; + + /** + * Load the Media Playback Technology (tech) + * Load/Create an instance of playback technology including element and API methods + * And append playback element in player div. + * + * @param {String} techName Name of the playback technology + * @param {String} source Video source + * @method loadTech_ + * @private + */ + + Player.prototype.loadTech_ = function loadTech_(techName, source) { + + // Pause and remove current playback technology + if (this.tech_) { + this.unloadTech_(); + } + + // get rid of the HTML5 video tag as soon as we are using another tech + if (techName !== 'Html5' && this.tag) { + _techTechJs2['default'].getTech('Html5').disposeMediaElement(this.tag); + this.tag.player = null; + this.tag = null; + } + + this.techName_ = techName; + + // Turn off API access because we're loading a new tech that might load asynchronously + this.isReady_ = false; + + // Grab tech-specific options from player options and add source and parent element to use. + var techOptions = _objectAssign2['default']({ + 'nativeControlsForTouch': this.options_.nativeControlsForTouch, + 'source': source, + 'playerId': this.id(), + 'techId': this.id() + '_' + techName + '_api', + 'videoTracks': this.videoTracks_, + 'textTracks': this.textTracks_, + 'audioTracks': this.audioTracks_, + 'autoplay': this.options_.autoplay, + 'preload': this.options_.preload, + 'loop': this.options_.loop, + 'muted': this.options_.muted, + 'poster': this.poster(), + 'language': this.language(), + 'vtt.js': this.options_['vtt.js'] + }, this.options_[techName.toLowerCase()]); + + if (this.tag) { + techOptions.tag = this.tag; + } + + if (source) { + this.currentType_ = source.type; + if (source.src === this.cache_.src && this.cache_.currentTime > 0) { + techOptions.startTime = this.cache_.currentTime; + } + + this.cache_.src = source.src; + } + + // Initialize tech instance + var techComponent = _techTechJs2['default'].getTech(techName); + // Support old behavior of techs being registered as components. + // Remove once that deprecated behavior is removed. + if (!techComponent) { + techComponent = _componentJs2['default'].getComponent(techName); + } + this.tech_ = new techComponent(techOptions); + + // player.triggerReady is always async, so don't need this to be async + this.tech_.ready(Fn.bind(this, this.handleTechReady_), true); + + _tracksTextTrackListConverterJs2['default'].jsonToTextTracks(this.textTracksJson_ || [], this.tech_); + + // Listen to all HTML5-defined events and trigger them on the player + this.on(this.tech_, 'loadstart', this.handleTechLoadStart_); + this.on(this.tech_, 'waiting', this.handleTechWaiting_); + this.on(this.tech_, 'canplay', this.handleTechCanPlay_); + this.on(this.tech_, 'canplaythrough', this.handleTechCanPlayThrough_); + this.on(this.tech_, 'playing', this.handleTechPlaying_); + this.on(this.tech_, 'ended', this.handleTechEnded_); + this.on(this.tech_, 'seeking', this.handleTechSeeking_); + this.on(this.tech_, 'seeked', this.handleTechSeeked_); + this.on(this.tech_, 'play', this.handleTechPlay_); + this.on(this.tech_, 'firstplay', this.handleTechFirstPlay_); + this.on(this.tech_, 'pause', this.handleTechPause_); + this.on(this.tech_, 'progress', this.handleTechProgress_); + this.on(this.tech_, 'durationchange', this.handleTechDurationChange_); + this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_); + this.on(this.tech_, 'error', this.handleTechError_); + this.on(this.tech_, 'suspend', this.handleTechSuspend_); + this.on(this.tech_, 'abort', this.handleTechAbort_); + this.on(this.tech_, 'emptied', this.handleTechEmptied_); + this.on(this.tech_, 'stalled', this.handleTechStalled_); + this.on(this.tech_, 'loadedmetadata', this.handleTechLoadedMetaData_); + this.on(this.tech_, 'loadeddata', this.handleTechLoadedData_); + this.on(this.tech_, 'timeupdate', this.handleTechTimeUpdate_); + this.on(this.tech_, 'ratechange', this.handleTechRateChange_); + this.on(this.tech_, 'volumechange', this.handleTechVolumeChange_); + this.on(this.tech_, 'texttrackchange', this.handleTechTextTrackChange_); + this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_); + this.on(this.tech_, 'posterchange', this.handleTechPosterChange_); + + this.usingNativeControls(this.techGet_('controls')); + + if (this.controls() && !this.usingNativeControls()) { + this.addTechControlsListeners_(); + } + + // Add the tech element in the DOM if it was not already there + // Make sure to not insert the original video element if using Html5 + if (this.tech_.el().parentNode !== this.el() && (techName !== 'Html5' || !this.tag)) { + Dom.insertElFirst(this.tech_.el(), this.el()); + } + + // Get rid of the original video tag reference after the first tech is loaded + if (this.tag) { + this.tag.player = null; + this.tag = null; + } + }; + + /** + * Unload playback technology + * + * @method unloadTech_ + * @private + */ + + Player.prototype.unloadTech_ = function unloadTech_() { + // Save the current text tracks so that we can reuse the same text tracks with the next tech + this.videoTracks_ = this.videoTracks(); + this.textTracks_ = this.textTracks(); + this.audioTracks_ = this.audioTracks(); + this.textTracksJson_ = _tracksTextTrackListConverterJs2['default'].textTracksToJson(this.tech_); + + this.isReady_ = false; + + this.tech_.dispose(); + + this.tech_ = false; + }; + + /** + * Return a reference to the current tech. + * It will only return a reference to the tech if given an object with the + * `IWillNotUseThisInPlugins` property on it. This is try and prevent misuse + * of techs by plugins. + * + * @param {Object} + * @return {Object} The Tech + * @method tech + */ + + Player.prototype.tech = function tech(safety) { + if (safety && safety.IWillNotUseThisInPlugins) { + return this.tech_; + } + var errorText = '\n Please make sure that you are not using this inside of a plugin.\n To disable this alert and error, please pass in an object with\n `IWillNotUseThisInPlugins` to the `tech` method. See\n https://github.com/videojs/video.js/issues/2617 for more info.\n '; + _globalWindow2['default'].alert(errorText); + throw new Error(errorText); + }; + + /** + * Set up click and touch listeners for the playback element + * + * On desktops, a click on the video itself will toggle playback, + * on a mobile device a click on the video toggles controls. + * (toggling controls is done by toggling the user state between active and + * inactive) + * A tap can signal that a user has become active, or has become inactive + * e.g. a quick tap on an iPhone movie should reveal the controls. Another + * quick tap should hide them again (signaling the user is in an inactive + * viewing state) + * In addition to this, we still want the user to be considered inactive after + * a few seconds of inactivity. + * Note: the only part of iOS interaction we can't mimic with this setup + * is a touch and hold on the video element counting as activity in order to + * keep the controls showing, but that shouldn't be an issue. A touch and hold + * on any controls will still keep the user active + * + * @private + * @method addTechControlsListeners_ + */ + + Player.prototype.addTechControlsListeners_ = function addTechControlsListeners_() { + // Make sure to remove all the previous listeners in case we are called multiple times. + this.removeTechControlsListeners_(); + + // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do + // trigger mousedown/up. + // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object + // Any touch events are set to block the mousedown event from happening + this.on(this.tech_, 'mousedown', this.handleTechClick_); + + // If the controls were hidden we don't want that to change without a tap event + // so we'll check if the controls were already showing before reporting user + // activity + this.on(this.tech_, 'touchstart', this.handleTechTouchStart_); + this.on(this.tech_, 'touchmove', this.handleTechTouchMove_); + this.on(this.tech_, 'touchend', this.handleTechTouchEnd_); + + // The tap listener needs to come after the touchend listener because the tap + // listener cancels out any reportedUserActivity when setting userActive(false) + this.on(this.tech_, 'tap', this.handleTechTap_); + }; + + /** + * Remove the listeners used for click and tap controls. This is needed for + * toggling to controls disabled, where a tap/touch should do nothing. + * + * @method removeTechControlsListeners_ + * @private + */ + + Player.prototype.removeTechControlsListeners_ = function removeTechControlsListeners_() { + // We don't want to just use `this.off()` because there might be other needed + // listeners added by techs that extend this. + this.off(this.tech_, 'tap', this.handleTechTap_); + this.off(this.tech_, 'touchstart', this.handleTechTouchStart_); + this.off(this.tech_, 'touchmove', this.handleTechTouchMove_); + this.off(this.tech_, 'touchend', this.handleTechTouchEnd_); + this.off(this.tech_, 'mousedown', this.handleTechClick_); + }; + + /** + * Player waits for the tech to be ready + * + * @method handleTechReady_ + * @private + */ + + Player.prototype.handleTechReady_ = function handleTechReady_() { + this.triggerReady(); + + // Keep the same volume as before + if (this.cache_.volume) { + this.techCall_('setVolume', this.cache_.volume); + } + + // Look if the tech found a higher resolution poster while loading + this.handleTechPosterChange_(); + + // Update the duration if available + this.handleTechDurationChange_(); + + // Chrome and Safari both have issues with autoplay. + // In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work. + // In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays) + // This fixes both issues. Need to wait for API, so it updates displays correctly + if (this.src() && this.tag && this.options_.autoplay && this.paused()) { + try { + delete this.tag.poster; // Chrome Fix. Fixed in Chrome v16. + } catch (e) { + _utilsLogJs2['default']('deleting tag.poster throws in some browsers', e); + } + this.play(); + } + }; + + /** + * Fired when the user agent begins looking for media data + * + * @private + * @method handleTechLoadStart_ + */ + + Player.prototype.handleTechLoadStart_ = function handleTechLoadStart_() { + // TODO: Update to use `emptied` event instead. See #1277. + + this.removeClass('vjs-ended'); + + // reset the error state + this.error(null); + + // If it's already playing we want to trigger a firstplay event now. + // The firstplay event relies on both the play and loadstart events + // which can happen in any order for a new source + if (!this.paused()) { + this.trigger('loadstart'); + this.trigger('firstplay'); + } else { + // reset the hasStarted state + this.hasStarted(false); + this.trigger('loadstart'); + } + }; + + /** + * Add/remove the vjs-has-started class + * + * @param {Boolean} hasStarted The value of true adds the class the value of false remove the class + * @return {Boolean} Boolean value if has started + * @private + * @method hasStarted + */ + + Player.prototype.hasStarted = function hasStarted(_hasStarted) { + if (_hasStarted !== undefined) { + // only update if this is a new value + if (this.hasStarted_ !== _hasStarted) { + this.hasStarted_ = _hasStarted; + if (_hasStarted) { + this.addClass('vjs-has-started'); + // trigger the firstplay event if this newly has played + this.trigger('firstplay'); + } else { + this.removeClass('vjs-has-started'); + } + } + return this; + } + return !!this.hasStarted_; + }; + + /** + * Fired whenever the media begins or resumes playback + * + * @private + * @method handleTechPlay_ + */ + + Player.prototype.handleTechPlay_ = function handleTechPlay_() { + this.removeClass('vjs-ended'); + this.removeClass('vjs-paused'); + this.addClass('vjs-playing'); + + // hide the poster when the user hits play + // https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play + this.hasStarted(true); + + this.trigger('play'); + }; + + /** + * Fired whenever the media begins waiting + * + * @private + * @method handleTechWaiting_ + */ + + Player.prototype.handleTechWaiting_ = function handleTechWaiting_() { + var _this2 = this; + + this.addClass('vjs-waiting'); + this.trigger('waiting'); + this.one('timeupdate', function () { + return _this2.removeClass('vjs-waiting'); + }); + }; + + /** + * A handler for events that signal that waiting has ended + * which is not consistent between browsers. See #1351 + * + * @private + * @method handleTechCanPlay_ + */ + + Player.prototype.handleTechCanPlay_ = function handleTechCanPlay_() { + this.removeClass('vjs-waiting'); + this.trigger('canplay'); + }; + + /** + * A handler for events that signal that waiting has ended + * which is not consistent between browsers. See #1351 + * + * @private + * @method handleTechCanPlayThrough_ + */ + + Player.prototype.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() { + this.removeClass('vjs-waiting'); + this.trigger('canplaythrough'); + }; + + /** + * A handler for events that signal that waiting has ended + * which is not consistent between browsers. See #1351 + * + * @private + * @method handleTechPlaying_ + */ + + Player.prototype.handleTechPlaying_ = function handleTechPlaying_() { + this.removeClass('vjs-waiting'); + this.trigger('playing'); + }; + + /** + * Fired whenever the player is jumping to a new time + * + * @private + * @method handleTechSeeking_ + */ + + Player.prototype.handleTechSeeking_ = function handleTechSeeking_() { + this.addClass('vjs-seeking'); + this.trigger('seeking'); + }; + + /** + * Fired when the player has finished jumping to a new time + * + * @private + * @method handleTechSeeked_ + */ + + Player.prototype.handleTechSeeked_ = function handleTechSeeked_() { + this.removeClass('vjs-seeking'); + this.trigger('seeked'); + }; + + /** + * Fired the first time a video is played + * Not part of the HLS spec, and we're not sure if this is the best + * implementation yet, so use sparingly. If you don't have a reason to + * prevent playback, use `myPlayer.one('play');` instead. + * + * @private + * @method handleTechFirstPlay_ + */ + + Player.prototype.handleTechFirstPlay_ = function handleTechFirstPlay_() { + //If the first starttime attribute is specified + //then we will start at the given offset in seconds + if (this.options_.starttime) { + this.currentTime(this.options_.starttime); + } + + this.addClass('vjs-has-started'); + this.trigger('firstplay'); + }; + + /** + * Fired whenever the media has been paused + * + * @private + * @method handleTechPause_ + */ + + Player.prototype.handleTechPause_ = function handleTechPause_() { + this.removeClass('vjs-playing'); + this.addClass('vjs-paused'); + this.trigger('pause'); + }; + + /** + * Fired while the user agent is downloading media data + * + * @private + * @method handleTechProgress_ + */ + + Player.prototype.handleTechProgress_ = function handleTechProgress_() { + this.trigger('progress'); + }; + + /** + * Fired when the end of the media resource is reached (currentTime == duration) + * + * @private + * @method handleTechEnded_ + */ + + Player.prototype.handleTechEnded_ = function handleTechEnded_() { + this.addClass('vjs-ended'); + if (this.options_.loop) { + this.currentTime(0); + this.play(); + } else if (!this.paused()) { + this.pause(); + } + + this.trigger('ended'); + }; + + /** + * Fired when the duration of the media resource is first known or changed + * + * @private + * @method handleTechDurationChange_ + */ + + Player.prototype.handleTechDurationChange_ = function handleTechDurationChange_() { + this.duration(this.techGet_('duration')); + }; + + /** + * Handle a click on the media element to play/pause + * + * @param {Object=} event Event object + * @private + * @method handleTechClick_ + */ + + Player.prototype.handleTechClick_ = function handleTechClick_(event) { + // We're using mousedown to detect clicks thanks to Flash, but mousedown + // will also be triggered with right-clicks, so we need to prevent that + if (event.button !== 0) return; + + // When controls are disabled a click should not toggle playback because + // the click is considered a control + if (this.controls()) { + if (this.paused()) { + this.play(); + } else { + this.pause(); + } + } + }; + + /** + * Handle a tap on the media element. It will toggle the user + * activity state, which hides and shows the controls. + * + * @private + * @method handleTechTap_ + */ + + Player.prototype.handleTechTap_ = function handleTechTap_() { + this.userActive(!this.userActive()); + }; + + /** + * Handle touch to start + * + * @private + * @method handleTechTouchStart_ + */ + + Player.prototype.handleTechTouchStart_ = function handleTechTouchStart_() { + this.userWasActive = this.userActive(); + }; + + /** + * Handle touch to move + * + * @private + * @method handleTechTouchMove_ + */ + + Player.prototype.handleTechTouchMove_ = function handleTechTouchMove_() { + if (this.userWasActive) { + this.reportUserActivity(); + } + }; + + /** + * Handle touch to end + * + * @private + * @method handleTechTouchEnd_ + */ + + Player.prototype.handleTechTouchEnd_ = function handleTechTouchEnd_(event) { + // Stop the mouse events from also happening + event.preventDefault(); + }; + + /** + * Fired when the player switches in or out of fullscreen mode + * + * @private + * @method handleFullscreenChange_ + */ + + Player.prototype.handleFullscreenChange_ = function handleFullscreenChange_() { + if (this.isFullscreen()) { + this.addClass('vjs-fullscreen'); + } else { + this.removeClass('vjs-fullscreen'); + } + }; + + /** + * native click events on the SWF aren't triggered on IE11, Win8.1RT + * use stageclick events triggered from inside the SWF instead + * + * @private + * @method handleStageClick_ + */ + + Player.prototype.handleStageClick_ = function handleStageClick_() { + this.reportUserActivity(); + }; + + /** + * Handle Tech Fullscreen Change + * + * @private + * @method handleTechFullscreenChange_ + */ + + Player.prototype.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) { + if (data) { + this.isFullscreen(data.isFullscreen); + } + this.trigger('fullscreenchange'); + }; + + /** + * Fires when an error occurred during the loading of an audio/video + * + * @private + * @method handleTechError_ + */ + + Player.prototype.handleTechError_ = function handleTechError_() { + var error = this.tech_.error(); + this.error(error); + }; + + /** + * Fires when the browser is intentionally not getting media data + * + * @private + * @method handleTechSuspend_ + */ + + Player.prototype.handleTechSuspend_ = function handleTechSuspend_() { + this.trigger('suspend'); + }; + + /** + * Fires when the loading of an audio/video is aborted + * + * @private + * @method handleTechAbort_ + */ + + Player.prototype.handleTechAbort_ = function handleTechAbort_() { + this.trigger('abort'); + }; + + /** + * Fires when the current playlist is empty + * + * @private + * @method handleTechEmptied_ + */ + + Player.prototype.handleTechEmptied_ = function handleTechEmptied_() { + this.trigger('emptied'); + }; + + /** + * Fires when the browser is trying to get media data, but data is not available + * + * @private + * @method handleTechStalled_ + */ + + Player.prototype.handleTechStalled_ = function handleTechStalled_() { + this.trigger('stalled'); + }; + + /** + * Fires when the browser has loaded meta data for the audio/video + * + * @private + * @method handleTechLoadedMetaData_ + */ + + Player.prototype.handleTechLoadedMetaData_ = function handleTechLoadedMetaData_() { + this.trigger('loadedmetadata'); + }; + + /** + * Fires when the browser has loaded the current frame of the audio/video + * + * @private + * @method handleTechLoadedData_ + */ + + Player.prototype.handleTechLoadedData_ = function handleTechLoadedData_() { + this.trigger('loadeddata'); + }; + + /** + * Fires when the current playback position has changed + * + * @private + * @method handleTechTimeUpdate_ + */ + + Player.prototype.handleTechTimeUpdate_ = function handleTechTimeUpdate_() { + this.trigger('timeupdate'); + }; + + /** + * Fires when the playing speed of the audio/video is changed + * + * @private + * @method handleTechRateChange_ + */ + + Player.prototype.handleTechRateChange_ = function handleTechRateChange_() { + this.trigger('ratechange'); + }; + + /** + * Fires when the volume has been changed + * + * @private + * @method handleTechVolumeChange_ + */ + + Player.prototype.handleTechVolumeChange_ = function handleTechVolumeChange_() { + this.trigger('volumechange'); + }; + + /** + * Fires when the text track has been changed + * + * @private + * @method handleTechTextTrackChange_ + */ + + Player.prototype.handleTechTextTrackChange_ = function handleTechTextTrackChange_() { + this.trigger('texttrackchange'); + }; + + /** + * Get object for cached values. + * + * @return {Object} + * @method getCache + */ + + Player.prototype.getCache = function getCache() { + return this.cache_; + }; + + /** + * Pass values to the playback tech + * + * @param {String=} method Method + * @param {Object=} arg Argument + * @private + * @method techCall_ + */ + + Player.prototype.techCall_ = function techCall_(method, arg) { + // If it's not ready yet, call method when it is + if (this.tech_ && !this.tech_.isReady_) { + this.tech_.ready(function () { + this[method](arg); + }, true); + + // Otherwise call method now + } else { + try { + this.tech_[method](arg); + } catch (e) { + _utilsLogJs2['default'](e); + throw e; + } + } + }; + + /** + * Get calls can't wait for the tech, and sometimes don't need to. + * + * @param {String} method Tech method + * @return {Method} + * @private + * @method techGet_ + */ + + Player.prototype.techGet_ = function techGet_(method) { + if (this.tech_ && this.tech_.isReady_) { + + // Flash likes to die and reload when you hide or reposition it. + // In these cases the object methods go away and we get errors. + // When that happens we'll catch the errors and inform tech that it's not ready any more. + try { + return this.tech_[method](); + } catch (e) { + // When building additional tech libs, an expected method may not be defined yet + if (this.tech_[method] === undefined) { + _utilsLogJs2['default']('Video.js: ' + method + ' method not defined for ' + this.techName_ + ' playback technology.', e); + } else { + // When a method isn't available on the object it throws a TypeError + if (e.name === 'TypeError') { + _utilsLogJs2['default']('Video.js: ' + method + ' unavailable on ' + this.techName_ + ' playback technology element.', e); + this.tech_.isReady_ = false; + } else { + _utilsLogJs2['default'](e); + } + } + throw e; + } + } + + return; + }; + + /** + * start media playback + * ```js + * myPlayer.play(); + * ``` + * + * @return {Player} self + * @method play + */ + + Player.prototype.play = function play() { + // Only calls the tech's play if we already have a src loaded + if (this.src() || this.currentSrc()) { + this.techCall_('play'); + } else { + this.tech_.one('loadstart', function () { + this.play(); + }); + } + + return this; + }; + + /** + * Pause the video playback + * ```js + * myPlayer.pause(); + * ``` + * + * @return {Player} self + * @method pause + */ + + Player.prototype.pause = function pause() { + this.techCall_('pause'); + return this; + }; + + /** + * Check if the player is paused + * ```js + * var isPaused = myPlayer.paused(); + * var isPlaying = !myPlayer.paused(); + * ``` + * + * @return {Boolean} false if the media is currently playing, or true otherwise + * @method paused + */ + + Player.prototype.paused = function paused() { + // The initial state of paused should be true (in Safari it's actually false) + return this.techGet_('paused') === false ? false : true; + }; + + /** + * Returns whether or not the user is "scrubbing". Scrubbing is when the user + * has clicked the progress bar handle and is dragging it along the progress bar. + * + * @param {Boolean} isScrubbing True/false the user is scrubbing + * @return {Boolean} The scrubbing status when getting + * @return {Object} The player when setting + * @method scrubbing + */ + + Player.prototype.scrubbing = function scrubbing(isScrubbing) { + if (isScrubbing !== undefined) { + this.scrubbing_ = !!isScrubbing; + + if (isScrubbing) { + this.addClass('vjs-scrubbing'); + } else { + this.removeClass('vjs-scrubbing'); + } + + return this; + } + + return this.scrubbing_; + }; + + /** + * Get or set the current time (in seconds) + * ```js + * // get + * var whereYouAt = myPlayer.currentTime(); + * // set + * myPlayer.currentTime(120); // 2 minutes into the video + * ``` + * + * @param {Number|String=} seconds The time to seek to + * @return {Number} The time in seconds, when not setting + * @return {Player} self, when the current time is set + * @method currentTime + */ + + Player.prototype.currentTime = function currentTime(seconds) { + if (seconds !== undefined) { + + this.techCall_('setCurrentTime', seconds); + + return this; + } + + // cache last currentTime and return. default to 0 seconds + // + // Caching the currentTime is meant to prevent a massive amount of reads on the tech's + // currentTime when scrubbing, but may not provide much performance benefit afterall. + // Should be tested. Also something has to read the actual current time or the cache will + // never get updated. + return this.cache_.currentTime = this.techGet_('currentTime') || 0; + }; + + /** + * Get the length in time of the video in seconds + * ```js + * var lengthOfVideo = myPlayer.duration(); + * ``` + * **NOTE**: The video must have started loading before the duration can be + * known, and in the case of Flash, may not be known until the video starts + * playing. + * + * @param {Number} seconds Duration when setting + * @return {Number} The duration of the video in seconds when getting + * @method duration + */ + + Player.prototype.duration = function duration(seconds) { + if (seconds === undefined) { + return this.cache_.duration || 0; + } + + seconds = parseFloat(seconds) || 0; + + // Standardize on Inifity for signaling video is live + if (seconds < 0) { + seconds = Infinity; + } + + if (seconds !== this.cache_.duration) { + // Cache the last set value for optimized scrubbing (esp. Flash) + this.cache_.duration = seconds; + + if (seconds === Infinity) { + this.addClass('vjs-live'); + } else { + this.removeClass('vjs-live'); + } + + this.trigger('durationchange'); + } + + return this; + }; + + /** + * Calculates how much time is left. + * ```js + * var timeLeft = myPlayer.remainingTime(); + * ``` + * Not a native video element function, but useful + * + * @return {Number} The time remaining in seconds + * @method remainingTime + */ + + Player.prototype.remainingTime = function remainingTime() { + return this.duration() - this.currentTime(); + }; + + // http://dev.w3.org/html5/spec/video.html#dom-media-buffered + // Buffered returns a timerange object. + // Kind of like an array of portions of the video that have been downloaded. + + /** + * Get a TimeRange object with the times of the video that have been downloaded + * If you just want the percent of the video that's been downloaded, + * use bufferedPercent. + * ```js + * // Number of different ranges of time have been buffered. Usually 1. + * numberOfRanges = bufferedTimeRange.length, + * // Time in seconds when the first range starts. Usually 0. + * firstRangeStart = bufferedTimeRange.start(0), + * // Time in seconds when the first range ends + * firstRangeEnd = bufferedTimeRange.end(0), + * // Length in seconds of the first time range + * firstRangeLength = firstRangeEnd - firstRangeStart; + * ``` + * + * @return {Object} A mock TimeRange object (following HTML spec) + * @method buffered + */ + + Player.prototype.buffered = function buffered() { + var buffered = this.techGet_('buffered'); + + if (!buffered || !buffered.length) { + buffered = _utilsTimeRangesJs.createTimeRange(0, 0); + } + + return buffered; + }; + + /** + * Get the percent (as a decimal) of the video that's been downloaded + * ```js + * var howMuchIsDownloaded = myPlayer.bufferedPercent(); + * ``` + * 0 means none, 1 means all. + * (This method isn't in the HTML5 spec, but it's very convenient) + * + * @return {Number} A decimal between 0 and 1 representing the percent + * @method bufferedPercent + */ + + Player.prototype.bufferedPercent = function bufferedPercent() { + return _utilsBufferJs.bufferedPercent(this.buffered(), this.duration()); + }; + + /** + * Get the ending time of the last buffered time range + * This is used in the progress bar to encapsulate all time ranges. + * + * @return {Number} The end of the last buffered time range + * @method bufferedEnd + */ + + Player.prototype.bufferedEnd = function bufferedEnd() { + var buffered = this.buffered(), + duration = this.duration(), + end = buffered.end(buffered.length - 1); + + if (end > duration) { + end = duration; + } + + return end; + }; + + /** + * Get or set the current volume of the media + * ```js + * // get + * var howLoudIsIt = myPlayer.volume(); + * // set + * myPlayer.volume(0.5); // Set volume to half + * ``` + * 0 is off (muted), 1.0 is all the way up, 0.5 is half way. + * + * @param {Number} percentAsDecimal The new volume as a decimal percent + * @return {Number} The current volume when getting + * @return {Player} self when setting + * @method volume + */ + + Player.prototype.volume = function volume(percentAsDecimal) { + var vol = undefined; + + if (percentAsDecimal !== undefined) { + vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); // Force value to between 0 and 1 + this.cache_.volume = vol; + this.techCall_('setVolume', vol); + + return this; + } + + // Default to 1 when returning current volume. + vol = parseFloat(this.techGet_('volume')); + return isNaN(vol) ? 1 : vol; + }; + + /** + * Get the current muted state, or turn mute on or off + * ```js + * // get + * var isVolumeMuted = myPlayer.muted(); + * // set + * myPlayer.muted(true); // mute the volume + * ``` + * + * @param {Boolean=} muted True to mute, false to unmute + * @return {Boolean} True if mute is on, false if not when getting + * @return {Player} self when setting mute + * @method muted + */ + + Player.prototype.muted = function muted(_muted) { + if (_muted !== undefined) { + this.techCall_('setMuted', _muted); + return this; + } + return this.techGet_('muted') || false; // Default to false + }; + + // Check if current tech can support native fullscreen + // (e.g. with built in controls like iOS, so not our flash swf) + /** + * Check to see if fullscreen is supported + * + * @return {Boolean} + * @method supportsFullScreen + */ + + Player.prototype.supportsFullScreen = function supportsFullScreen() { + return this.techGet_('supportsFullScreen') || false; + }; + + /** + * Check if the player is in fullscreen mode + * ```js + * // get + * var fullscreenOrNot = myPlayer.isFullscreen(); + * // set + * myPlayer.isFullscreen(true); // tell the player it's in fullscreen + * ``` + * NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official + * property and instead document.fullscreenElement is used. But isFullscreen is + * still a valuable property for internal player workings. + * + * @param {Boolean=} isFS Update the player's fullscreen state + * @return {Boolean} true if fullscreen false if not when getting + * @return {Player} self when setting + * @method isFullscreen + */ + + Player.prototype.isFullscreen = function isFullscreen(isFS) { + if (isFS !== undefined) { + this.isFullscreen_ = !!isFS; + return this; + } + return !!this.isFullscreen_; + }; + + /** + * Increase the size of the video to full screen + * ```js + * myPlayer.requestFullscreen(); + * ``` + * In some browsers, full screen is not supported natively, so it enters + * "full window mode", where the video fills the browser window. + * In browsers and devices that support native full screen, sometimes the + * browser's default controls will be shown, and not the Video.js custom skin. + * This includes most mobile devices (iOS, Android) and older versions of + * Safari. + * + * @return {Player} self + * @method requestFullscreen + */ + + Player.prototype.requestFullscreen = function requestFullscreen() { + var fsApi = _fullscreenApiJs2['default']; + + this.isFullscreen(true); + + if (fsApi.requestFullscreen) { + // the browser supports going fullscreen at the element level so we can + // take the controls fullscreen as well as the video + + // Trigger fullscreenchange event after change + // We have to specifically add this each time, and remove + // when canceling fullscreen. Otherwise if there's multiple + // players on a page, they would all be reacting to the same fullscreen + // events + Events.on(_globalDocument2['default'], fsApi.fullscreenchange, Fn.bind(this, function documentFullscreenChange(e) { + this.isFullscreen(_globalDocument2['default'][fsApi.fullscreenElement]); + + // If cancelling fullscreen, remove event listener. + if (this.isFullscreen() === false) { + Events.off(_globalDocument2['default'], fsApi.fullscreenchange, documentFullscreenChange); + } + + this.trigger('fullscreenchange'); + })); + + this.el_[fsApi.requestFullscreen](); + } else if (this.tech_.supportsFullScreen()) { + // we can't take the video.js controls fullscreen but we can go fullscreen + // with native controls + this.techCall_('enterFullScreen'); + } else { + // fullscreen isn't supported so we'll just stretch the video element to + // fill the viewport + this.enterFullWindow(); + this.trigger('fullscreenchange'); + } + + return this; + }; + + /** + * Return the video to its normal size after having been in full screen mode + * ```js + * myPlayer.exitFullscreen(); + * ``` + * + * @return {Player} self + * @method exitFullscreen + */ + + Player.prototype.exitFullscreen = function exitFullscreen() { + var fsApi = _fullscreenApiJs2['default']; + this.isFullscreen(false); + + // Check for browser element fullscreen support + if (fsApi.requestFullscreen) { + _globalDocument2['default'][fsApi.exitFullscreen](); + } else if (this.tech_.supportsFullScreen()) { + this.techCall_('exitFullScreen'); + } else { + this.exitFullWindow(); + this.trigger('fullscreenchange'); + } + + return this; + }; + + /** + * When fullscreen isn't supported we can stretch the video container to as wide as the browser will let us. + * + * @method enterFullWindow + */ + + Player.prototype.enterFullWindow = function enterFullWindow() { + this.isFullWindow = true; + + // Storing original doc overflow value to return to when fullscreen is off + this.docOrigOverflow = _globalDocument2['default'].documentElement.style.overflow; + + // Add listener for esc key to exit fullscreen + Events.on(_globalDocument2['default'], 'keydown', Fn.bind(this, this.fullWindowOnEscKey)); + + // Hide any scroll bars + _globalDocument2['default'].documentElement.style.overflow = 'hidden'; + + // Apply fullscreen styles + Dom.addElClass(_globalDocument2['default'].body, 'vjs-full-window'); + + this.trigger('enterFullWindow'); + }; + + /** + * Check for call to either exit full window or full screen on ESC key + * + * @param {String} event Event to check for key press + * @method fullWindowOnEscKey + */ + + Player.prototype.fullWindowOnEscKey = function fullWindowOnEscKey(event) { + if (event.keyCode === 27) { + if (this.isFullscreen() === true) { + this.exitFullscreen(); + } else { + this.exitFullWindow(); + } + } + }; + + /** + * Exit full window + * + * @method exitFullWindow + */ + + Player.prototype.exitFullWindow = function exitFullWindow() { + this.isFullWindow = false; + Events.off(_globalDocument2['default'], 'keydown', this.fullWindowOnEscKey); + + // Unhide scroll bars. + _globalDocument2['default'].documentElement.style.overflow = this.docOrigOverflow; + + // Remove fullscreen styles + Dom.removeElClass(_globalDocument2['default'].body, 'vjs-full-window'); + + // Resize the box, controller, and poster to original sizes + // this.positionAll(); + this.trigger('exitFullWindow'); + }; + + /** + * Check whether the player can play a given mimetype + * + * @param {String} type The mimetype to check + * @return {String} 'probably', 'maybe', or '' (empty string) + * @method canPlayType + */ + + Player.prototype.canPlayType = function canPlayType(type) { + var can = undefined; + + // Loop through each playback technology in the options order + for (var i = 0, j = this.options_.techOrder; i < j.length; i++) { + var techName = _utilsToTitleCaseJs2['default'](j[i]); + var tech = _techTechJs2['default'].getTech(techName); + + // Support old behavior of techs being registered as components. + // Remove once that deprecated behavior is removed. + if (!tech) { + tech = _componentJs2['default'].getComponent(techName); + } + + // Check if the current tech is defined before continuing + if (!tech) { + _utilsLogJs2['default'].error('The "' + techName + '" tech is undefined. Skipped browser support check for that tech.'); + continue; + } + + // Check if the browser supports this technology + if (tech.isSupported()) { + can = tech.canPlayType(type); + + if (can) { + return can; + } + } + } + + return ''; + }; + + /** + * Select source based on tech-order or source-order + * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise, + * defaults to tech-order selection + * + * @param {Array} sources The sources for a media asset + * @return {Object|Boolean} Object of source and tech order, otherwise false + * @method selectSource + */ + + Player.prototype.selectSource = function selectSource(sources) { + var _this3 = this; + + // Get only the techs specified in `techOrder` that exist and are supported by the + // current platform + var techs = this.options_.techOrder.map(_utilsToTitleCaseJs2['default']).map(function (techName) { + // `Component.getComponent(...)` is for support of old behavior of techs + // being registered as components. + // Remove once that deprecated behavior is removed. + return [techName, _techTechJs2['default'].getTech(techName) || _componentJs2['default'].getComponent(techName)]; + }).filter(function (_ref) { + var techName = _ref[0]; + var tech = _ref[1]; + + // Check if the current tech is defined before continuing + if (tech) { + // Check if the browser supports this technology + return tech.isSupported(); + } + + _utilsLogJs2['default'].error('The "' + techName + '" tech is undefined. Skipped browser support check for that tech.'); + return false; + }); + + // Iterate over each `innerArray` element once per `outerArray` element and execute + // `tester` with both. If `tester` returns a non-falsy value, exit early and return + // that value. + var findFirstPassingTechSourcePair = function findFirstPassingTechSourcePair(outerArray, innerArray, tester) { + var found = undefined; + + outerArray.some(function (outerChoice) { + return innerArray.some(function (innerChoice) { + found = tester(outerChoice, innerChoice); + + if (found) { + return true; + } + }); + }); + + return found; + }; + + var foundSourceAndTech = undefined; + var flip = function flip(fn) { + return function (a, b) { + return fn(b, a); + }; + }; + var finder = function finder(_ref2, source) { + var techName = _ref2[0]; + var tech = _ref2[1]; + + if (tech.canPlaySource(source, _this3.options_[techName.toLowerCase()])) { + return { source: source, tech: techName }; + } + }; + + // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources + // to select from them based on their priority. + if (this.options_.sourceOrder) { + // Source-first ordering + foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder)); + } else { + // Tech-first ordering + foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder); + } + + return foundSourceAndTech || false; + }; + + /** + * The source function updates the video source + * There are three types of variables you can pass as the argument. + * **URL String**: A URL to the the video file. Use this method if you are sure + * the current playback technology (HTML5/Flash) can support the source you + * provide. Currently only MP4 files can be used in both HTML5 and Flash. + * ```js + * myPlayer.src("http://www.example.com/path/to/video.mp4"); + * ``` + * **Source Object (or element):* * A javascript object containing information + * about the source file. Use this method if you want the player to determine if + * it can support the file using the type information. + * ```js + * myPlayer.src({ type: "video/mp4", src: "http://www.example.com/path/to/video.mp4" }); + * ``` + * **Array of Source Objects:* * To provide multiple versions of the source so + * that it can be played using HTML5 across browsers you can use an array of + * source objects. Video.js will detect which version is supported and load that + * file. + * ```js + * myPlayer.src([ + * { type: "video/mp4", src: "http://www.example.com/path/to/video.mp4" }, + * { type: "video/webm", src: "http://www.example.com/path/to/video.webm" }, + * { type: "video/ogg", src: "http://www.example.com/path/to/video.ogv" } + * ]); + * ``` + * + * @param {String|Object|Array=} source The source URL, object, or array of sources + * @return {String} The current video source when getting + * @return {String} The player when setting + * @method src + */ + + Player.prototype.src = function src(source) { + if (source === undefined) { + return this.techGet_('src'); + } + + var currentTech = _techTechJs2['default'].getTech(this.techName_); + // Support old behavior of techs being registered as components. + // Remove once that deprecated behavior is removed. + if (!currentTech) { + currentTech = _componentJs2['default'].getComponent(this.techName_); + } + + // case: Array of source objects to choose from and pick the best to play + if (Array.isArray(source)) { + this.sourceList_(source); + + // case: URL String (http://myvideo...) + } else if (typeof source === 'string') { + // create a source object from the string + this.src({ src: source }); + + // case: Source object { src: '', type: '' ... } + } else if (source instanceof Object) { + // check if the source has a type and the loaded tech cannot play the source + // if there's no type we'll just try the current tech + if (source.type && !currentTech.canPlaySource(source, this.options_[this.techName_.toLowerCase()])) { + // create a source list with the current source and send through + // the tech loop to check for a compatible technology + this.sourceList_([source]); + } else { + this.cache_.src = source.src; + this.currentType_ = source.type || ''; + + // wait until the tech is ready to set the source + this.ready(function () { + + // The setSource tech method was added with source handlers + // so older techs won't support it + // We need to check the direct prototype for the case where subclasses + // of the tech do not support source handlers + if (currentTech.prototype.hasOwnProperty('setSource')) { + this.techCall_('setSource', source); + } else { + this.techCall_('src', source.src); + } + + if (this.options_.preload === 'auto') { + this.load(); + } + + if (this.options_.autoplay) { + this.play(); + } + + // Set the source synchronously if possible (#2326) + }, true); + } + } + + return this; + }; + + /** + * Handle an array of source objects + * + * @param {Array} sources Array of source objects + * @private + * @method sourceList_ + */ + + Player.prototype.sourceList_ = function sourceList_(sources) { + var sourceTech = this.selectSource(sources); + + if (sourceTech) { + if (sourceTech.tech === this.techName_) { + // if this technology is already loaded, set the source + this.src(sourceTech.source); + } else { + // load this technology with the chosen source + this.loadTech_(sourceTech.tech, sourceTech.source); + } + } else { + // We need to wrap this in a timeout to give folks a chance to add error event handlers + this.setTimeout(function () { + this.error({ code: 4, message: this.localize(this.options_.notSupportedMessage) }); + }, 0); + + // we could not find an appropriate tech, but let's still notify the delegate that this is it + // this needs a better comment about why this is needed + this.triggerReady(); + } + }; + + /** + * Begin loading the src data. + * + * @return {Player} Returns the player + * @method load + */ + + Player.prototype.load = function load() { + this.techCall_('load'); + return this; + }; + + /** + * Reset the player. Loads the first tech in the techOrder, + * and calls `reset` on the tech`. + * + * @return {Player} Returns the player + * @method reset + */ + + Player.prototype.reset = function reset() { + this.loadTech_(_utilsToTitleCaseJs2['default'](this.options_.techOrder[0]), null); + this.techCall_('reset'); + return this; + }; + + /** + * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4 + * Can be used in conjuction with `currentType` to assist in rebuilding the current source object. + * + * @return {String} The current source + * @method currentSrc + */ + + Player.prototype.currentSrc = function currentSrc() { + return this.techGet_('currentSrc') || this.cache_.src || ''; + }; + + /** + * Get the current source type e.g. video/mp4 + * This can allow you rebuild the current source object so that you could load the same + * source and tech later + * + * @return {String} The source MIME type + * @method currentType + */ + + Player.prototype.currentType = function currentType() { + return this.currentType_ || ''; + }; + + /** + * Get or set the preload attribute + * + * @param {Boolean} value Boolean to determine if preload should be used + * @return {String} The preload attribute value when getting + * @return {Player} Returns the player when setting + * @method preload + */ + + Player.prototype.preload = function preload(value) { + if (value !== undefined) { + this.techCall_('setPreload', value); + this.options_.preload = value; + return this; + } + return this.techGet_('preload'); + }; + + /** + * Get or set the autoplay attribute. + * + * @param {Boolean} value Boolean to determine if video should autoplay + * @return {String} The autoplay attribute value when getting + * @return {Player} Returns the player when setting + * @method autoplay + */ + + Player.prototype.autoplay = function autoplay(value) { + if (value !== undefined) { + this.techCall_('setAutoplay', value); + this.options_.autoplay = value; + return this; + } + return this.techGet_('autoplay', value); + }; + + /** + * Get or set the loop attribute on the video element. + * + * @param {Boolean} value Boolean to determine if video should loop + * @return {String} The loop attribute value when getting + * @return {Player} Returns the player when setting + * @method loop + */ + + Player.prototype.loop = function loop(value) { + if (value !== undefined) { + this.techCall_('setLoop', value); + this.options_['loop'] = value; + return this; + } + return this.techGet_('loop'); + }; + + /** + * Get or set the poster image source url + * + * ##### EXAMPLE: + * ```js + * // get + * var currentPoster = myPlayer.poster(); + * // set + * myPlayer.poster('http://example.com/myImage.jpg'); + * ``` + * + * @param {String=} src Poster image source URL + * @return {String} poster URL when getting + * @return {Player} self when setting + * @method poster + */ + + Player.prototype.poster = function poster(src) { + if (src === undefined) { + return this.poster_; + } + + // The correct way to remove a poster is to set as an empty string + // other falsey values will throw errors + if (!src) { + src = ''; + } + + // update the internal poster variable + this.poster_ = src; + + // update the tech's poster + this.techCall_('setPoster', src); + + // alert components that the poster has been set + this.trigger('posterchange'); + + return this; + }; + + /** + * Some techs (e.g. YouTube) can provide a poster source in an + * asynchronous way. We want the poster component to use this + * poster source so that it covers up the tech's controls. + * (YouTube's play button). However we only want to use this + * soruce if the player user hasn't set a poster through + * the normal APIs. + * + * @private + * @method handleTechPosterChange_ + */ + + Player.prototype.handleTechPosterChange_ = function handleTechPosterChange_() { + if (!this.poster_ && this.tech_ && this.tech_.poster) { + this.poster_ = this.tech_.poster() || ''; + + // Let components know the poster has changed + this.trigger('posterchange'); + } + }; + + /** + * Get or set whether or not the controls are showing. + * + * @param {Boolean} bool Set controls to showing or not + * @return {Boolean} Controls are showing + * @method controls + */ + + Player.prototype.controls = function controls(bool) { + if (bool !== undefined) { + bool = !!bool; // force boolean + // Don't trigger a change event unless it actually changed + if (this.controls_ !== bool) { + this.controls_ = bool; + + if (this.usingNativeControls()) { + this.techCall_('setControls', bool); + } + + if (bool) { + this.removeClass('vjs-controls-disabled'); + this.addClass('vjs-controls-enabled'); + this.trigger('controlsenabled'); + + if (!this.usingNativeControls()) { + this.addTechControlsListeners_(); + } + } else { + this.removeClass('vjs-controls-enabled'); + this.addClass('vjs-controls-disabled'); + this.trigger('controlsdisabled'); + + if (!this.usingNativeControls()) { + this.removeTechControlsListeners_(); + } + } + } + return this; + } + return !!this.controls_; + }; + + /** + * Toggle native controls on/off. Native controls are the controls built into + * devices (e.g. default iPhone controls), Flash, or other techs + * (e.g. Vimeo Controls) + * **This should only be set by the current tech, because only the tech knows + * if it can support native controls** + * + * @param {Boolean} bool True signals that native controls are on + * @return {Player} Returns the player + * @private + * @method usingNativeControls + */ + + Player.prototype.usingNativeControls = function usingNativeControls(bool) { + if (bool !== undefined) { + bool = !!bool; // force boolean + // Don't trigger a change event unless it actually changed + if (this.usingNativeControls_ !== bool) { + this.usingNativeControls_ = bool; + if (bool) { + this.addClass('vjs-using-native-controls'); + + /** + * player is using the native device controls + * + * @event usingnativecontrols + * @memberof Player + * @instance + * @private + */ + this.trigger('usingnativecontrols'); + } else { + this.removeClass('vjs-using-native-controls'); + + /** + * player is using the custom HTML controls + * + * @event usingcustomcontrols + * @memberof Player + * @instance + * @private + */ + this.trigger('usingcustomcontrols'); + } + } + return this; + } + return !!this.usingNativeControls_; + }; + + /** + * Set or get the current MediaError + * + * @param {*} err A MediaError or a String/Number to be turned into a MediaError + * @return {MediaError|null} when getting + * @return {Player} when setting + * @method error + */ + + Player.prototype.error = function error(err) { + if (err === undefined) { + return this.error_ || null; + } + + // restoring to default + if (err === null) { + this.error_ = err; + this.removeClass('vjs-error'); + this.errorDisplay.close(); + return this; + } + + // error instance + if (err instanceof _mediaErrorJs2['default']) { + this.error_ = err; + } else { + this.error_ = new _mediaErrorJs2['default'](err); + } + + // add the vjs-error classname to the player + this.addClass('vjs-error'); + + // log the name of the error type and any message + // ie8 just logs "[object object]" if you just log the error object + _utilsLogJs2['default'].error('(CODE:' + this.error_.code + ' ' + _mediaErrorJs2['default'].errorTypes[this.error_.code] + ')', this.error_.message, this.error_); + + // fire an error event on the player + this.trigger('error'); + + return this; + }; + + /** + * Returns whether or not the player is in the "ended" state. + * + * @return {Boolean} True if the player is in the ended state, false if not. + * @method ended + */ + + Player.prototype.ended = function ended() { + return this.techGet_('ended'); + }; + + /** + * Returns whether or not the player is in the "seeking" state. + * + * @return {Boolean} True if the player is in the seeking state, false if not. + * @method seeking + */ + + Player.prototype.seeking = function seeking() { + return this.techGet_('seeking'); + }; + + /** + * Returns the TimeRanges of the media that are currently available + * for seeking to. + * + * @return {TimeRanges} the seekable intervals of the media timeline + * @method seekable + */ + + Player.prototype.seekable = function seekable() { + return this.techGet_('seekable'); + }; + + /** + * Report user activity + * + * @param {Object} event Event object + * @method reportUserActivity + */ + + Player.prototype.reportUserActivity = function reportUserActivity(event) { + this.userActivity_ = true; + }; + + /** + * Get/set if user is active + * + * @param {Boolean} bool Value when setting + * @return {Boolean} Value if user is active user when getting + * @method userActive + */ + + Player.prototype.userActive = function userActive(bool) { + if (bool !== undefined) { + bool = !!bool; + if (bool !== this.userActive_) { + this.userActive_ = bool; + if (bool) { + // If the user was inactive and is now active we want to reset the + // inactivity timer + this.userActivity_ = true; + this.removeClass('vjs-user-inactive'); + this.addClass('vjs-user-active'); + this.trigger('useractive'); + } else { + // We're switching the state to inactive manually, so erase any other + // activity + this.userActivity_ = false; + + // Chrome/Safari/IE have bugs where when you change the cursor it can + // trigger a mousemove event. This causes an issue when you're hiding + // the cursor when the user is inactive, and a mousemove signals user + // activity. Making it impossible to go into inactive mode. Specifically + // this happens in fullscreen when we really need to hide the cursor. + // + // When this gets resolved in ALL browsers it can be removed + // https://code.google.com/p/chromium/issues/detail?id=103041 + if (this.tech_) { + this.tech_.one('mousemove', function (e) { + e.stopPropagation(); + e.preventDefault(); + }); + } + + this.removeClass('vjs-user-active'); + this.addClass('vjs-user-inactive'); + this.trigger('userinactive'); + } + } + return this; + } + return this.userActive_; + }; + + /** + * Listen for user activity based on timeout value + * + * @private + * @method listenForUserActivity_ + */ + + Player.prototype.listenForUserActivity_ = function listenForUserActivity_() { + var mouseInProgress = undefined, + lastMoveX = undefined, + lastMoveY = undefined; + + var handleActivity = Fn.bind(this, this.reportUserActivity); + + var handleMouseMove = function handleMouseMove(e) { + // #1068 - Prevent mousemove spamming + // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970 + if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) { + lastMoveX = e.screenX; + lastMoveY = e.screenY; + handleActivity(); + } + }; + + var handleMouseDown = function handleMouseDown() { + handleActivity(); + // For as long as the they are touching the device or have their mouse down, + // we consider them active even if they're not moving their finger or mouse. + // So we want to continue to update that they are active + this.clearInterval(mouseInProgress); + // Setting userActivity=true now and setting the interval to the same time + // as the activityCheck interval (250) should ensure we never miss the + // next activityCheck + mouseInProgress = this.setInterval(handleActivity, 250); + }; + + var handleMouseUp = function handleMouseUp(event) { + handleActivity(); + // Stop the interval that maintains activity if the mouse/touch is down + this.clearInterval(mouseInProgress); + }; + + // Any mouse movement will be considered user activity + this.on('mousedown', handleMouseDown); + this.on('mousemove', handleMouseMove); + this.on('mouseup', handleMouseUp); + + // Listen for keyboard navigation + // Shouldn't need to use inProgress interval because of key repeat + this.on('keydown', handleActivity); + this.on('keyup', handleActivity); + + // Run an interval every 250 milliseconds instead of stuffing everything into + // the mousemove/touchmove function itself, to prevent performance degradation. + // `this.reportUserActivity` simply sets this.userActivity_ to true, which + // then gets picked up by this loop + // http://ejohn.org/blog/learning-from-twitter/ + var inactivityTimeout = undefined; + var activityCheck = this.setInterval(function () { + // Check to see if mouse/touch activity has happened + if (this.userActivity_) { + // Reset the activity tracker + this.userActivity_ = false; + + // If the user state was inactive, set the state to active + this.userActive(true); + + // Clear any existing inactivity timeout to start the timer over + this.clearTimeout(inactivityTimeout); + + var timeout = this.options_['inactivityTimeout']; + if (timeout > 0) { + // In milliseconds, if no more activity has occurred the + // user will be considered inactive + inactivityTimeout = this.setTimeout(function () { + // Protect against the case where the inactivityTimeout can trigger just + // before the next user activity is picked up by the activityCheck loop + // causing a flicker + if (!this.userActivity_) { + this.userActive(false); + } + }, timeout); + } + } + }, 250); + }; + + /** + * Gets or sets the current playback rate. A playback rate of + * 1.0 represents normal speed and 0.5 would indicate half-speed + * playback, for instance. + * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate + * + * @param {Number} rate New playback rate to set. + * @return {Number} Returns the new playback rate when setting + * @return {Number} Returns the current playback rate when getting + * @method playbackRate + */ + + Player.prototype.playbackRate = function playbackRate(rate) { + if (rate !== undefined) { + this.techCall_('setPlaybackRate', rate); + return this; + } + + if (this.tech_ && this.tech_['featuresPlaybackRate']) { + return this.techGet_('playbackRate'); + } else { + return 1.0; + } + }; + + /** + * Gets or sets the audio flag + * + * @param {Boolean} bool True signals that this is an audio player. + * @return {Boolean} Returns true if player is audio, false if not when getting + * @return {Player} Returns the player if setting + * @private + * @method isAudio + */ + + Player.prototype.isAudio = function isAudio(bool) { + if (bool !== undefined) { + this.isAudio_ = !!bool; + return this; + } + + return !!this.isAudio_; + }; + + /** + * Returns the current state of network activity for the element, from + * the codes in the list below. + * - NETWORK_EMPTY (numeric value 0) + * The element has not yet been initialised. All attributes are in + * their initial states. + * - NETWORK_IDLE (numeric value 1) + * The element's resource selection algorithm is active and has + * selected a resource, but it is not actually using the network at + * this time. + * - NETWORK_LOADING (numeric value 2) + * The user agent is actively trying to download data. + * - NETWORK_NO_SOURCE (numeric value 3) + * The element's resource selection algorithm is active, but it has + * not yet found a resource to use. + * + * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states + * @return {Number} the current network activity state + * @method networkState + */ + + Player.prototype.networkState = function networkState() { + return this.techGet_('networkState'); + }; + + /** + * Returns a value that expresses the current state of the element + * with respect to rendering the current playback position, from the + * codes in the list below. + * - HAVE_NOTHING (numeric value 0) + * No information regarding the media resource is available. + * - HAVE_METADATA (numeric value 1) + * Enough of the resource has been obtained that the duration of the + * resource is available. + * - HAVE_CURRENT_DATA (numeric value 2) + * Data for the immediate current playback position is available. + * - HAVE_FUTURE_DATA (numeric value 3) + * Data for the immediate current playback position is available, as + * well as enough data for the user agent to advance the current + * playback position in the direction of playback. + * - HAVE_ENOUGH_DATA (numeric value 4) + * The user agent estimates that enough data is available for + * playback to proceed uninterrupted. + * + * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate + * @return {Number} the current playback rendering state + * @method readyState + */ + + Player.prototype.readyState = function readyState() { + return this.techGet_('readyState'); + }; + + /** + * Get a video track list + * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist + * + * @return {VideoTrackList} thes current video track list + * @method videoTracks + */ + + Player.prototype.videoTracks = function videoTracks() { + // if we have not yet loadTech_, we create videoTracks_ + // these will be passed to the tech during loading + if (!this.tech_) { + this.videoTracks_ = this.videoTracks_ || new _tracksVideoTrackListJs2['default'](); + return this.videoTracks_; + } + + return this.tech_.videoTracks(); + }; + + /** + * Get an audio track list + * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist + * + * @return {AudioTrackList} thes current audio track list + * @method audioTracks + */ + + Player.prototype.audioTracks = function audioTracks() { + // if we have not yet loadTech_, we create videoTracks_ + // these will be passed to the tech during loading + if (!this.tech_) { + this.audioTracks_ = this.audioTracks_ || new _tracksAudioTrackListJs2['default'](); + return this.audioTracks_; + } + + return this.tech_.audioTracks(); + }; + + /* + * Text tracks are tracks of timed text events. + * Captions - text displayed over the video for the hearing impaired + * Subtitles - text displayed over the video for those who don't understand language in the video + * Chapters - text displayed in a menu allowing the user to jump to particular points (chapters) in the video + * Descriptions (not supported yet) - audio descriptions that are read back to the user by a screen reading device + */ + + /** + * Get an array of associated text tracks. captions, subtitles, chapters, descriptions + * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks + * + * @return {Array} Array of track objects + * @method textTracks + */ + + Player.prototype.textTracks = function textTracks() { + // cannot use techGet_ directly because it checks to see whether the tech is ready. + // Flash is unlikely to be ready in time but textTracks should still work. + return this.tech_ && this.tech_['textTracks'](); + }; + + /** + * Get an array of remote text tracks + * + * @return {Array} + * @method remoteTextTracks + */ + + Player.prototype.remoteTextTracks = function remoteTextTracks() { + return this.tech_ && this.tech_['remoteTextTracks'](); + }; + + /** + * Get an array of remote html track elements + * + * @return {HTMLTrackElement[]} + * @method remoteTextTrackEls + */ + + Player.prototype.remoteTextTrackEls = function remoteTextTrackEls() { + return this.tech_ && this.tech_['remoteTextTrackEls'](); + }; + + /** + * Add a text track + * In addition to the W3C settings we allow adding additional info through options. + * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack + * + * @param {String} kind Captions, subtitles, chapters, descriptions, or metadata + * @param {String=} label Optional label + * @param {String=} language Optional language + * @method addTextTrack + */ + + Player.prototype.addTextTrack = function addTextTrack(kind, label, language) { + return this.tech_ && this.tech_['addTextTrack'](kind, label, language); + }; + + /** + * Add a remote text track + * + * @param {Object} options Options for remote text track + * @method addRemoteTextTrack + */ + + Player.prototype.addRemoteTextTrack = function addRemoteTextTrack(options) { + return this.tech_ && this.tech_['addRemoteTextTrack'](options); + }; + + /** + * Remove a remote text track + * + * @param {Object} track Remote text track to remove + * @method removeRemoteTextTrack + */ + // destructure the input into an object with a track argument, defaulting to arguments[0] + // default the whole argument to an empty object if nothing was passed in + + Player.prototype.removeRemoteTextTrack = function removeRemoteTextTrack() { + var _ref3 = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + var _ref3$track = _ref3.track; + var track = _ref3$track === undefined ? arguments[0] : _ref3$track; + // jshint ignore:line + this.tech_ && this.tech_['removeRemoteTextTrack'](track); + }; + + /** + * Get video width + * + * @return {Number} Video width + * @method videoWidth + */ + + Player.prototype.videoWidth = function videoWidth() { + return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0; + }; + + /** + * Get video height + * + * @return {Number} Video height + * @method videoHeight + */ + + Player.prototype.videoHeight = function videoHeight() { + return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0; + }; + + // Methods to add support for + // initialTime: function(){ return this.techCall_('initialTime'); }, + // startOffsetTime: function(){ return this.techCall_('startOffsetTime'); }, + // played: function(){ return this.techCall_('played'); }, + // defaultPlaybackRate: function(){ return this.techCall_('defaultPlaybackRate'); }, + // defaultMuted: function(){ return this.techCall_('defaultMuted'); } + + /** + * The player's language code + * NOTE: The language should be set in the player options if you want the + * the controls to be built with a specific language. Changing the lanugage + * later will not update controls text. + * + * @param {String} code The locale string + * @return {String} The locale string when getting + * @return {Player} self when setting + * @method language + */ + + Player.prototype.language = function language(code) { + if (code === undefined) { + return this.language_; + } + + this.language_ = ('' + code).toLowerCase(); + return this; + }; + + /** + * Get the player's language dictionary + * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time + * Languages specified directly in the player options have precedence + * + * @return {Array} Array of languages + * @method languages + */ + + Player.prototype.languages = function languages() { + return _utilsMergeOptionsJs2['default'](Player.prototype.options_.languages, this.languages_); + }; + + /** + * Converts track info to JSON + * + * @return {Object} JSON object of options + * @method toJSON + */ + + Player.prototype.toJSON = function toJSON() { + var options = _utilsMergeOptionsJs2['default'](this.options_); + var tracks = options.tracks; + + options.tracks = []; + + for (var i = 0; i < tracks.length; i++) { + var track = tracks[i]; + + // deep merge tracks and null out player so no circular references + track = _utilsMergeOptionsJs2['default'](track); + track.player = undefined; + options.tracks[i] = track; + } + + return options; + }; + + /** + * Creates a simple modal dialog (an instance of the `ModalDialog` + * component) that immediately overlays the player with arbitrary + * content and removes itself when closed. + * + * @param {String|Function|Element|Array|Null} content + * Same as `ModalDialog#content`'s param of the same name. + * + * The most straight-forward usage is to provide a string or DOM + * element. + * + * @param {Object} [options] + * Extra options which will be passed on to the `ModalDialog`. + * + * @return {ModalDialog} + */ + + Player.prototype.createModal = function createModal(content, options) { + var player = this; + + options = options || {}; + options.content = content || ''; + + var modal = new _modalDialog2['default'](player, options); + + player.addChild(modal); + modal.on('dispose', function () { + player.removeChild(modal); + }); + + return modal.open(); + }; + + /** + * Gets tag settings + * + * @param {Element} tag The player tag + * @return {Array} An array of sources and track objects + * @static + * @method getTagSettings + */ + + Player.getTagSettings = function getTagSettings(tag) { + var baseOptions = { + 'sources': [], + 'tracks': [] + }; + + var tagOptions = Dom.getElAttributes(tag); + var dataSetup = tagOptions['data-setup']; + + // Check if data-setup attr exists. + if (dataSetup !== null) { + // Parse options JSON + + var _safeParseTuple = _safeJsonParseTuple2['default'](dataSetup || '{}'); + + var err = _safeParseTuple[0]; + var data = _safeParseTuple[1]; + + if (err) { + _utilsLogJs2['default'].error(err); + } + _objectAssign2['default'](tagOptions, data); + } + + _objectAssign2['default'](baseOptions, tagOptions); + + // Get tag children settings + if (tag.hasChildNodes()) { + var children = tag.childNodes; + + for (var i = 0, j = children.length; i < j; i++) { + var child = children[i]; + // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/ + var childName = child.nodeName.toLowerCase(); + if (childName === 'source') { + baseOptions.sources.push(Dom.getElAttributes(child)); + } else if (childName === 'track') { + baseOptions.tracks.push(Dom.getElAttributes(child)); + } + } + } + + return baseOptions; + }; + + return Player; +})(_componentJs2['default']); + +Player.players = {}; + +var navigator = _globalWindow2['default'].navigator; +/* + * Player instance options, surfaced using options + * options = Player.prototype.options_ + * Make changes in options, not here. + * + * @type {Object} + * @private + */ +Player.prototype.options_ = { + // Default order of fallback technology + techOrder: ['html5', 'flash'], + // techOrder: ['flash','html5'], + + html5: {}, + flash: {}, + + // defaultVolume: 0.85, + defaultVolume: 0.00, // The freakin seaguls are driving me crazy! + + // default inactivity timeout + inactivityTimeout: 2000, + + // default playback rates + playbackRates: [], + // Add playback rate selection by adding rates + // 'playbackRates': [0.5, 1, 1.5, 2], + + // Included control sets + children: ['mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'controlBar', 'errorDisplay', 'textTrackSettings'], + + language: _globalDocument2['default'].getElementsByTagName('html')[0].getAttribute('lang') || navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language || 'en', + + // locales and their language translations + languages: {}, + + // Default message to show when a video cannot be played. + notSupportedMessage: 'No compatible source was found for this media.' +}; + +/** + * Fired when the user agent begins looking for media data + * + * @event loadstart + */ +Player.prototype.handleTechLoadStart_; + +/** + * Fired when the player has initial duration and dimension information + * + * @event loadedmetadata + */ +Player.prototype.handleLoadedMetaData_; + +/** + * Fired when the player has downloaded data at the current playback position + * + * @event loadeddata + */ +Player.prototype.handleLoadedData_; + +/** + * Fired when the user is active, e.g. moves the mouse over the player + * + * @event useractive + */ +Player.prototype.handleUserActive_; + +/** + * Fired when the user is inactive, e.g. a short delay after the last mouse move or control interaction + * + * @event userinactive + */ +Player.prototype.handleUserInactive_; + +/** + * Fired when the current playback position has changed * + * During playback this is fired every 15-250 milliseconds, depending on the + * playback technology in use. + * + * @event timeupdate + */ +Player.prototype.handleTimeUpdate_; + +/** + * Fired when video playback ends + * + * @event ended + */ +Player.prototype.handleTechEnded_; + +/** + * Fired when the volume changes + * + * @event volumechange + */ +Player.prototype.handleVolumeChange_; + +/** + * Fired when an error occurs + * + * @event error + */ +Player.prototype.handleError_; + +Player.prototype.flexNotSupported_ = function () { + var elem = _globalDocument2['default'].createElement('i'); + + // Note: We don't actually use flexBasis (or flexOrder), but it's one of the more + // common flex features that we can rely on when checking for flex support. + return !('flexBasis' in elem.style || 'webkitFlexBasis' in elem.style || 'mozFlexBasis' in elem.style || 'msFlexBasis' in elem.style || 'msFlexOrder' in elem.style) /* IE10-specific (2012 flex spec) */; +}; + +_componentJs2['default'].registerComponent('Player', Player); +exports['default'] = Player; +module.exports = exports['default']; +// If empty string, make it a parsable json object. + +},{"./big-play-button.js":63,"./component.js":67,"./control-bar/control-bar.js":70,"./error-display.js":103,"./fullscreen-api.js":106,"./loading-spinner.js":107,"./media-error.js":108,"./modal-dialog":112,"./poster-image.js":117,"./tech/html5.js":122,"./tech/loader.js":123,"./tech/tech.js":124,"./tracks/audio-track-list.js":125,"./tracks/text-track-display.js":130,"./tracks/text-track-list-converter.js":131,"./tracks/text-track-settings.js":133,"./tracks/video-track-list.js":138,"./utils/browser.js":140,"./utils/buffer.js":141,"./utils/dom.js":142,"./utils/events.js":143,"./utils/fn.js":144,"./utils/guid.js":146,"./utils/log.js":147,"./utils/merge-options.js":148,"./utils/stylesheet.js":149,"./utils/time-ranges.js":150,"./utils/to-title-case.js":151,"global/document":7,"global/window":8,"object.assign":53,"safe-json-parse/tuple":58}],114:[function(_dereq_,module,exports){ +/** + * @file plugins.js + */ +'use strict'; + +exports.__esModule = true; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _playerJs = _dereq_('./player.js'); + +var _playerJs2 = _interopRequireDefault(_playerJs); + +/** + * The method for registering a video.js plugin + * + * @param {String} name The name of the plugin + * @param {Function} init The function that is run when the player inits + * @method plugin + */ +var plugin = function plugin(name, init) { + _playerJs2['default'].prototype[name] = init; +}; + +exports['default'] = plugin; +module.exports = exports['default']; + +},{"./player.js":113}],115:[function(_dereq_,module,exports){ +/** + * @file popup-button.js + */ +'use strict'; + +exports.__esModule = true; + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _clickableComponentJs = _dereq_('../clickable-component.js'); + +var _clickableComponentJs2 = _interopRequireDefault(_clickableComponentJs); + +var _componentJs = _dereq_('../component.js'); + +var _componentJs2 = _interopRequireDefault(_componentJs); + +var _popupJs = _dereq_('./popup.js'); + +var _popupJs2 = _interopRequireDefault(_popupJs); + +var _utilsDomJs = _dereq_('../utils/dom.js'); + +var Dom = _interopRequireWildcard(_utilsDomJs); + +var _utilsFnJs = _dereq_('../utils/fn.js'); + +var Fn = _interopRequireWildcard(_utilsFnJs); + +var _utilsToTitleCaseJs = _dereq_('../utils/to-title-case.js'); + +var _utilsToTitleCaseJs2 = _interopRequireDefault(_utilsToTitleCaseJs); + +/** + * A button class with a popup control + * + * @param {Player|Object} player + * @param {Object=} options + * @extends ClickableComponent + * @class PopupButton + */ + +var PopupButton = (function (_ClickableComponent) { + _inherits(PopupButton, _ClickableComponent); + + function PopupButton(player) { + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + _classCallCheck(this, PopupButton); + + _ClickableComponent.call(this, player, options); + + this.update(); + } + + /** + * Update popup + * + * @method update + */ + + PopupButton.prototype.update = function update() { + var popup = this.createPopup(); + + if (this.popup) { + this.removeChild(this.popup); + } + + this.popup = popup; + this.addChild(popup); + + if (this.items && this.items.length === 0) { + this.hide(); + } else if (this.items && this.items.length > 1) { + this.show(); + } + }; + + /** + * Create popup - Override with specific functionality for component + * + * @return {Popup} The constructed popup + * @method createPopup + */ + + PopupButton.prototype.createPopup = function createPopup() {}; + + /** + * Create the component's DOM element + * + * @return {Element} + * @method createEl + */ + + PopupButton.prototype.createEl = function createEl() { + return _ClickableComponent.prototype.createEl.call(this, 'div', { + className: this.buildCSSClass() + }); + }; + + /** + * Allow sub components to stack CSS class names + * + * @return {String} The constructed class name + * @method buildCSSClass + */ + + PopupButton.prototype.buildCSSClass = function buildCSSClass() { + var menuButtonClass = 'vjs-menu-button'; + + // If the inline option is passed, we want to use different styles altogether. + if (this.options_.inline === true) { + menuButtonClass += '-inline'; + } else { + menuButtonClass += '-popup'; + } + + return 'vjs-menu-button ' + menuButtonClass + ' ' + _ClickableComponent.prototype.buildCSSClass.call(this); + }; + + return PopupButton; +})(_clickableComponentJs2['default']); + +_componentJs2['default'].registerComponent('PopupButton', PopupButton); +exports['default'] = PopupButton; +module.exports = exports['default']; + +},{"../clickable-component.js":65,"../component.js":67,"../utils/dom.js":142,"../utils/fn.js":144,"../utils/to-title-case.js":151,"./popup.js":116}],116:[function(_dereq_,module,exports){ +/** + * @file popup.js + */ +'use strict'; + +exports.__esModule = true; + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _componentJs = _dereq_('../component.js'); + +var _componentJs2 = _interopRequireDefault(_componentJs); + +var _utilsDomJs = _dereq_('../utils/dom.js'); + +var Dom = _interopRequireWildcard(_utilsDomJs); + +var _utilsFnJs = _dereq_('../utils/fn.js'); + +var Fn = _interopRequireWildcard(_utilsFnJs); + +var _utilsEventsJs = _dereq_('../utils/events.js'); + +var Events = _interopRequireWildcard(_utilsEventsJs); + +/** + * The Popup component is used to build pop up controls. + * + * @extends Component + * @class Popup + */ + +var Popup = (function (_Component) { + _inherits(Popup, _Component); + + function Popup() { + _classCallCheck(this, Popup); + + _Component.apply(this, arguments); + } + + /** + * Add a popup item to the popup + * + * @param {Object|String} component Component or component type to add + * @method addItem + */ + + Popup.prototype.addItem = function addItem(component) { + this.addChild(component); + component.on('click', Fn.bind(this, function () { + this.unlockShowing(); + })); + }; + + /** + * Create the component's DOM element + * + * @return {Element} + * @method createEl + */ + + Popup.prototype.createEl = function createEl() { + var contentElType = this.options_.contentElType || 'ul'; + this.contentEl_ = Dom.createEl(contentElType, { + className: 'vjs-menu-content' + }); + var el = _Component.prototype.createEl.call(this, 'div', { + append: this.contentEl_, + className: 'vjs-menu' + }); + el.appendChild(this.contentEl_); + + // Prevent clicks from bubbling up. Needed for Popup Buttons, + // where a click on the parent is significant + Events.on(el, 'click', function (event) { + event.preventDefault(); + event.stopImmediatePropagation(); + }); + + return el; + }; + + return Popup; +})(_componentJs2['default']); + +_componentJs2['default'].registerComponent('Popup', Popup); +exports['default'] = Popup; +module.exports = exports['default']; + +},{"../component.js":67,"../utils/dom.js":142,"../utils/events.js":143,"../utils/fn.js":144}],117:[function(_dereq_,module,exports){ +/** + * @file poster-image.js + */ +'use strict'; + +exports.__esModule = true; + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _clickableComponentJs = _dereq_('./clickable-component.js'); + +var _clickableComponentJs2 = _interopRequireDefault(_clickableComponentJs); + +var _componentJs = _dereq_('./component.js'); + +var _componentJs2 = _interopRequireDefault(_componentJs); + +var _utilsFnJs = _dereq_('./utils/fn.js'); + +var Fn = _interopRequireWildcard(_utilsFnJs); + +var _utilsDomJs = _dereq_('./utils/dom.js'); + +var Dom = _interopRequireWildcard(_utilsDomJs); + +var _utilsBrowserJs = _dereq_('./utils/browser.js'); + +var browser = _interopRequireWildcard(_utilsBrowserJs); + +/** + * The component that handles showing the poster image. + * + * @param {Player|Object} player + * @param {Object=} options + * @extends Button + * @class PosterImage + */ + +var PosterImage = (function (_ClickableComponent) { + _inherits(PosterImage, _ClickableComponent); + + function PosterImage(player, options) { + _classCallCheck(this, PosterImage); + + _ClickableComponent.call(this, player, options); + + this.update(); + player.on('posterchange', Fn.bind(this, this.update)); + } + + /** + * Clean up the poster image + * + * @method dispose + */ + + PosterImage.prototype.dispose = function dispose() { + this.player().off('posterchange', this.update); + _ClickableComponent.prototype.dispose.call(this); + }; + + /** + * Create the poster's image element + * + * @return {Element} + * @method createEl + */ + + PosterImage.prototype.createEl = function createEl() { + var el = Dom.createEl('div', { + className: 'vjs-poster', + + // Don't want poster to be tabbable. + tabIndex: -1 + }); + + // To ensure the poster image resizes while maintaining its original aspect + // ratio, use a div with `background-size` when available. For browsers that + // do not support `background-size` (e.g. IE8), fall back on using a regular + // img element. + if (!browser.BACKGROUND_SIZE_SUPPORTED) { + this.fallbackImg_ = Dom.createEl('img'); + el.appendChild(this.fallbackImg_); + } + + return el; + }; + + /** + * Event handler for updates to the player's poster source + * + * @method update + */ + + PosterImage.prototype.update = function update() { + var url = this.player().poster(); + + this.setSrc(url); + + // If there's no poster source we should display:none on this component + // so it's not still clickable or right-clickable + if (url) { + this.show(); + } else { + this.hide(); + } + }; + + /** + * Set the poster source depending on the display method + * + * @param {String} url The URL to the poster source + * @method setSrc + */ + + PosterImage.prototype.setSrc = function setSrc(url) { + if (this.fallbackImg_) { + this.fallbackImg_.src = url; + } else { + var backgroundImage = ''; + // Any falsey values should stay as an empty string, otherwise + // this will throw an extra error + if (url) { + backgroundImage = 'url("' + url + '")'; + } + + this.el_.style.backgroundImage = backgroundImage; + } + }; + + /** + * Event handler for clicks on the poster image + * + * @method handleClick + */ + + PosterImage.prototype.handleClick = function handleClick() { + // We don't want a click to trigger playback when controls are disabled + // but CSS should be hiding the poster to prevent that from happening + if (this.player_.paused()) { + this.player_.play(); + } else { + this.player_.pause(); + } + }; + + return PosterImage; +})(_clickableComponentJs2['default']); + +_componentJs2['default'].registerComponent('PosterImage', PosterImage); +exports['default'] = PosterImage; +module.exports = exports['default']; + +},{"./clickable-component.js":65,"./component.js":67,"./utils/browser.js":140,"./utils/dom.js":142,"./utils/fn.js":144}],118:[function(_dereq_,module,exports){ +/** + * @file setup.js + * + * Functions for automatically setting up a player + * based on the data-setup attribute of the video tag + */ +'use strict'; + +exports.__esModule = true; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +var _utilsEventsJs = _dereq_('./utils/events.js'); + +var Events = _interopRequireWildcard(_utilsEventsJs); + +var _globalDocument = _dereq_('global/document'); + +var _globalDocument2 = _interopRequireDefault(_globalDocument); + +var _globalWindow = _dereq_('global/window'); + +var _globalWindow2 = _interopRequireDefault(_globalWindow); + +var _windowLoaded = false; +var videojs = undefined; + +// Automatically set up any tags that have a data-setup attribute +var autoSetup = function autoSetup() { + // One day, when we stop supporting IE8, go back to this, but in the meantime...*hack hack hack* + // var vids = Array.prototype.slice.call(document.getElementsByTagName('video')); + // var audios = Array.prototype.slice.call(document.getElementsByTagName('audio')); + // var mediaEls = vids.concat(audios); + + // Because IE8 doesn't support calling slice on a node list, we need to loop through each list of elements + // to build up a new, combined list of elements. + var vids = _globalDocument2['default'].getElementsByTagName('video'); + var audios = _globalDocument2['default'].getElementsByTagName('audio'); + var mediaEls = []; + if (vids && vids.length > 0) { + for (var i = 0, e = vids.length; i < e; i++) { + mediaEls.push(vids[i]); + } + } + if (audios && audios.length > 0) { + for (var i = 0, e = audios.length; i < e; i++) { + mediaEls.push(audios[i]); + } + } + + // Check if any media elements exist + if (mediaEls && mediaEls.length > 0) { + + for (var i = 0, e = mediaEls.length; i < e; i++) { + var mediaEl = mediaEls[i]; + + // Check if element exists, has getAttribute func. + // IE seems to consider typeof el.getAttribute == 'object' instead of 'function' like expected, at least when loading the player immediately. + if (mediaEl && mediaEl.getAttribute) { + + // Make sure this player hasn't already been set up. + if (mediaEl['player'] === undefined) { + var options = mediaEl.getAttribute('data-setup'); + + // Check if data-setup attr exists. + // We only auto-setup if they've added the data-setup attr. + if (options !== null) { + // Create new video.js instance. + var player = videojs(mediaEl); + } + } + + // If getAttribute isn't defined, we need to wait for the DOM. + } else { + autoSetupTimeout(1); + break; + } + } + + // No videos were found, so keep looping unless page is finished loading. + } else if (!_windowLoaded) { + autoSetupTimeout(1); + } +}; + +// Pause to let the DOM keep processing +var autoSetupTimeout = function autoSetupTimeout(wait, vjs) { + if (vjs) { + videojs = vjs; + } + + setTimeout(autoSetup, wait); +}; + +if (_globalDocument2['default'].readyState === 'complete') { + _windowLoaded = true; +} else { + Events.one(_globalWindow2['default'], 'load', function () { + _windowLoaded = true; + }); +} + +var hasLoaded = function hasLoaded() { + return _windowLoaded; +}; + +exports.autoSetup = autoSetup; +exports.autoSetupTimeout = autoSetupTimeout; +exports.hasLoaded = hasLoaded; + +},{"./utils/events.js":143,"global/document":7,"global/window":8}],119:[function(_dereq_,module,exports){ +/** + * @file slider.js + */ +'use strict'; + +exports.__esModule = true; + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _componentJs = _dereq_('../component.js'); + +var _componentJs2 = _interopRequireDefault(_componentJs); + +var _utilsDomJs = _dereq_('../utils/dom.js'); + +var Dom = _interopRequireWildcard(_utilsDomJs); + +var _objectAssign = _dereq_('object.assign'); + +var _objectAssign2 = _interopRequireDefault(_objectAssign); + +/** + * The base functionality for sliders like the volume bar and seek bar + * + * @param {Player|Object} player + * @param {Object=} options + * @extends Component + * @class Slider + */ + +var Slider = (function (_Component) { + _inherits(Slider, _Component); + + function Slider(player, options) { + _classCallCheck(this, Slider); + + _Component.call(this, player, options); + + // Set property names to bar to match with the child Slider class is looking for + this.bar = this.getChild(this.options_.barName); + + // Set a horizontal or vertical class on the slider depending on the slider type + this.vertical(!!this.options_.vertical); + + this.on('mousedown', this.handleMouseDown); + this.on('touchstart', this.handleMouseDown); + this.on('focus', this.handleFocus); + this.on('blur', this.handleBlur); + this.on('click', this.handleClick); + + this.on(player, 'controlsvisible', this.update); + this.on(player, this.playerEvent, this.update); + } + + /** + * Create the component's DOM element + * + * @param {String} type Type of element to create + * @param {Object=} props List of properties in Object form + * @return {Element} + * @method createEl + */ + + Slider.prototype.createEl = function createEl(type) { + var props = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + var attributes = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; + + // Add the slider element class to all sub classes + props.className = props.className + ' vjs-slider'; + props = _objectAssign2['default']({ + tabIndex: 0 + }, props); + + attributes = _objectAssign2['default']({ + 'role': 'slider', + 'aria-valuenow': 0, + 'aria-valuemin': 0, + 'aria-valuemax': 100, + tabIndex: 0 + }, attributes); + + return _Component.prototype.createEl.call(this, type, props, attributes); + }; + + /** + * Handle mouse down on slider + * + * @param {Object} event Mouse down event object + * @method handleMouseDown + */ + + Slider.prototype.handleMouseDown = function handleMouseDown(event) { + var doc = this.bar.el_.ownerDocument; + + event.preventDefault(); + Dom.blockTextSelection(); + + this.addClass('vjs-sliding'); + this.trigger('slideractive'); + + this.on(doc, 'mousemove', this.handleMouseMove); + this.on(doc, 'mouseup', this.handleMouseUp); + this.on(doc, 'touchmove', this.handleMouseMove); + this.on(doc, 'touchend', this.handleMouseUp); + + this.handleMouseMove(event); + }; + + /** + * To be overridden by a subclass + * + * @method handleMouseMove + */ + + Slider.prototype.handleMouseMove = function handleMouseMove() {}; + + /** + * Handle mouse up on Slider + * + * @method handleMouseUp + */ + + Slider.prototype.handleMouseUp = function handleMouseUp() { + var doc = this.bar.el_.ownerDocument; + + Dom.unblockTextSelection(); + + this.removeClass('vjs-sliding'); + this.trigger('sliderinactive'); + + this.off(doc, 'mousemove', this.handleMouseMove); + this.off(doc, 'mouseup', this.handleMouseUp); + this.off(doc, 'touchmove', this.handleMouseMove); + this.off(doc, 'touchend', this.handleMouseUp); + + this.update(); + }; + + /** + * Update slider + * + * @method update + */ + + Slider.prototype.update = function update() { + // In VolumeBar init we have a setTimeout for update that pops and update to the end of the + // execution stack. The player is destroyed before then update will cause an error + if (!this.el_) return; + + // If scrubbing, we could use a cached value to make the handle keep up with the user's mouse. + // On HTML5 browsers scrubbing is really smooth, but some flash players are slow, so we might want to utilize this later. + // var progress = (this.player_.scrubbing()) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration(); + var progress = this.getPercent(); + var bar = this.bar; + + // If there's no bar... + if (!bar) return; + + // Protect against no duration and other division issues + if (typeof progress !== 'number' || progress !== progress || progress < 0 || progress === Infinity) { + progress = 0; + } + + // Convert to a percentage for setting + var percentage = (progress * 100).toFixed(2) + '%'; + + // Set the new bar width or height + if (this.vertical()) { + bar.el().style.height = percentage; + } else { + bar.el().style.width = percentage; + } + }; + + /** + * Calculate distance for slider + * + * @param {Object} event Event object + * @method calculateDistance + */ + + Slider.prototype.calculateDistance = function calculateDistance(event) { + var position = Dom.getPointerPosition(this.el_, event); + if (this.vertical()) { + return position.y; + } + return position.x; + }; + + /** + * Handle on focus for slider + * + * @method handleFocus + */ + + Slider.prototype.handleFocus = function handleFocus() { + this.on(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress); + }; + + /** + * Handle key press for slider + * + * @param {Object} event Event object + * @method handleKeyPress + */ + + Slider.prototype.handleKeyPress = function handleKeyPress(event) { + if (event.which === 37 || event.which === 40) { + // Left and Down Arrows + event.preventDefault(); + this.stepBack(); + } else if (event.which === 38 || event.which === 39) { + // Up and Right Arrows + event.preventDefault(); + this.stepForward(); + } + }; + + /** + * Handle on blur for slider + * + * @method handleBlur + */ + + Slider.prototype.handleBlur = function handleBlur() { + this.off(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress); + }; + + /** + * Listener for click events on slider, used to prevent clicks + * from bubbling up to parent elements like button menus. + * + * @param {Object} event Event object + * @method handleClick + */ + + Slider.prototype.handleClick = function handleClick(event) { + event.stopImmediatePropagation(); + event.preventDefault(); + }; + + /** + * Get/set if slider is horizontal for vertical + * + * @param {Boolean} bool True if slider is vertical, false is horizontal + * @return {Boolean} True if slider is vertical, false is horizontal + * @method vertical + */ + + Slider.prototype.vertical = function vertical(bool) { + if (bool === undefined) { + return this.vertical_ || false; + } + + this.vertical_ = !!bool; + + if (this.vertical_) { + this.addClass('vjs-slider-vertical'); + } else { + this.addClass('vjs-slider-horizontal'); + } + + return this; + }; + + return Slider; +})(_componentJs2['default']); + +_componentJs2['default'].registerComponent('Slider', Slider); +exports['default'] = Slider; +module.exports = exports['default']; + +},{"../component.js":67,"../utils/dom.js":142,"object.assign":53}],120:[function(_dereq_,module,exports){ +/** + * @file flash-rtmp.js + */ +'use strict'; + +exports.__esModule = true; +function FlashRtmpDecorator(Flash) { + Flash.streamingFormats = { + 'rtmp/mp4': 'MP4', + 'rtmp/flv': 'FLV' + }; + + Flash.streamFromParts = function (connection, stream) { + return connection + '&' + stream; + }; + + Flash.streamToParts = function (src) { + var parts = { + connection: '', + stream: '' + }; + + if (!src) return parts; + + // Look for the normal URL separator we expect, '&'. + // If found, we split the URL into two pieces around the + // first '&'. + var connEnd = src.search(/&(?!\w+=)/); + var streamBegin = undefined; + if (connEnd !== -1) { + streamBegin = connEnd + 1; + } else { + // If there's not a '&', we use the last '/' as the delimiter. + connEnd = streamBegin = src.lastIndexOf('/') + 1; + if (connEnd === 0) { + // really, there's not a '/'? + connEnd = streamBegin = src.length; + } + } + parts.connection = src.substring(0, connEnd); + parts.stream = src.substring(streamBegin, src.length); + + return parts; + }; + + Flash.isStreamingType = function (srcType) { + return srcType in Flash.streamingFormats; + }; + + // RTMP has four variations, any string starting + // with one of these protocols should be valid + Flash.RTMP_RE = /^rtmp[set]?:\/\//i; + + Flash.isStreamingSrc = function (src) { + return Flash.RTMP_RE.test(src); + }; + + /** + * A source handler for RTMP urls + * @type {Object} + */ + Flash.rtmpSourceHandler = {}; + + /** + * Check if Flash can play the given videotype + * @param {String} type The mimetype to check + * @return {String} 'probably', 'maybe', or '' (empty string) + */ + Flash.rtmpSourceHandler.canPlayType = function (type) { + if (Flash.isStreamingType(type)) { + return 'maybe'; + } + + return ''; + }; + + /** + * Check if Flash can handle the source natively + * @param {Object} source The source object + * @param {Object} options The options passed to the tech + * @return {String} 'probably', 'maybe', or '' (empty string) + */ + Flash.rtmpSourceHandler.canHandleSource = function (source, options) { + var can = Flash.rtmpSourceHandler.canPlayType(source.type); + + if (can) { + return can; + } + + if (Flash.isStreamingSrc(source.src)) { + return 'maybe'; + } + + return ''; + }; + + /** + * Pass the source to the flash object + * Adaptive source handlers will have more complicated workflows before passing + * video data to the video element + * @param {Object} source The source object + * @param {Flash} tech The instance of the Flash tech + * @param {Object} options The options to pass to the source + */ + Flash.rtmpSourceHandler.handleSource = function (source, tech, options) { + var srcParts = Flash.streamToParts(source.src); + + tech['setRtmpConnection'](srcParts.connection); + tech['setRtmpStream'](srcParts.stream); + }; + + // Register the native source handler + Flash.registerSourceHandler(Flash.rtmpSourceHandler); + + return Flash; +} + +exports['default'] = FlashRtmpDecorator; +module.exports = exports['default']; + +},{}],121:[function(_dereq_,module,exports){ +/** + * @file flash.js + * VideoJS-SWF - Custom Flash Player with HTML5-ish API + * https://github.com/zencoder/video-js-swf + * Not using setupTriggers. Using global onEvent func to distribute events + */ + +'use strict'; + +exports.__esModule = true; + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _tech = _dereq_('./tech'); + +var _tech2 = _interopRequireDefault(_tech); + +var _utilsDomJs = _dereq_('../utils/dom.js'); + +var Dom = _interopRequireWildcard(_utilsDomJs); + +var _utilsUrlJs = _dereq_('../utils/url.js'); + +var Url = _interopRequireWildcard(_utilsUrlJs); + +var _utilsTimeRangesJs = _dereq_('../utils/time-ranges.js'); + +var _flashRtmp = _dereq_('./flash-rtmp'); + +var _flashRtmp2 = _interopRequireDefault(_flashRtmp); + +var _component = _dereq_('../component'); + +var _component2 = _interopRequireDefault(_component); + +var _globalWindow = _dereq_('global/window'); + +var _globalWindow2 = _interopRequireDefault(_globalWindow); + +var _objectAssign = _dereq_('object.assign'); + +var _objectAssign2 = _interopRequireDefault(_objectAssign); + +var navigator = _globalWindow2['default'].navigator; +/** + * Flash Media Controller - Wrapper for fallback SWF API + * + * @param {Object=} options Object of option names and values + * @param {Function=} ready Ready callback function + * @extends Tech + * @class Flash + */ + +var Flash = (function (_Tech) { + _inherits(Flash, _Tech); + + function Flash(options, ready) { + _classCallCheck(this, Flash); + + _Tech.call(this, options, ready); + + // Set the source when ready + if (options.source) { + this.ready(function () { + this.setSource(options.source); + }, true); + } + + // Having issues with Flash reloading on certain page actions (hide/resize/fullscreen) in certain browsers + // This allows resetting the playhead when we catch the reload + if (options.startTime) { + this.ready(function () { + this.load(); + this.play(); + this.currentTime(options.startTime); + }, true); + } + + // Add global window functions that the swf expects + // A 4.x workflow we weren't able to solve for in 5.0 + // because of the need to hard code these functions + // into the swf for security reasons + _globalWindow2['default'].videojs = _globalWindow2['default'].videojs || {}; + _globalWindow2['default'].videojs.Flash = _globalWindow2['default'].videojs.Flash || {}; + _globalWindow2['default'].videojs.Flash.onReady = Flash.onReady; + _globalWindow2['default'].videojs.Flash.onEvent = Flash.onEvent; + _globalWindow2['default'].videojs.Flash.onError = Flash.onError; + + this.on('seeked', function () { + this.lastSeekTarget_ = undefined; + }); + } + + // Create setters and getters for attributes + + /** + * Create the component's DOM element + * + * @return {Element} + * @method createEl + */ + + Flash.prototype.createEl = function createEl() { + var options = this.options_; + + // If video.js is hosted locally you should also set the location + // for the hosted swf, which should be relative to the page (not video.js) + // Otherwise this adds a CDN url. + // The CDN also auto-adds a swf URL for that specific version. + if (!options.swf) { + options.swf = "<%= asset_path('video-js.swf') %>"; + } + + // Generate ID for swf object + var objId = options.techId; + + // Merge default flashvars with ones passed in to init + var flashVars = _objectAssign2['default']({ + + // SWF Callback Functions + 'readyFunction': 'videojs.Flash.onReady', + 'eventProxyFunction': 'videojs.Flash.onEvent', + 'errorEventProxyFunction': 'videojs.Flash.onError', + + // Player Settings + 'autoplay': options.autoplay, + 'preload': options.preload, + 'loop': options.loop, + 'muted': options.muted + + }, options.flashVars); + + // Merge default parames with ones passed in + var params = _objectAssign2['default']({ + 'wmode': 'opaque', // Opaque is needed to overlay controls, but can affect playback performance + 'bgcolor': '#000000' // Using bgcolor prevents a white flash when the object is loading + }, options.params); + + // Merge default attributes with ones passed in + var attributes = _objectAssign2['default']({ + 'id': objId, + 'name': objId, // Both ID and Name needed or swf to identify itself + 'class': 'vjs-tech' + }, options.attributes); + + this.el_ = Flash.embed(options.swf, flashVars, params, attributes); + this.el_.tech = this; + + return this.el_; + }; + + /** + * Play for flash tech + * + * @method play + */ + + Flash.prototype.play = function play() { + if (this.ended()) { + this.setCurrentTime(0); + } + this.el_.vjs_play(); + }; + + /** + * Pause for flash tech + * + * @method pause + */ + + Flash.prototype.pause = function pause() { + this.el_.vjs_pause(); + }; + + /** + * Get/set video + * + * @param {Object=} src Source object + * @return {Object} + * @method src + */ + + Flash.prototype.src = function src(_src) { + if (_src === undefined) { + return this.currentSrc(); + } + + // Setting src through `src` not `setSrc` will be deprecated + return this.setSrc(_src); + }; + + /** + * Set video + * + * @param {Object=} src Source object + * @deprecated + * @method setSrc + */ + + Flash.prototype.setSrc = function setSrc(src) { + // Make sure source URL is absolute. + src = Url.getAbsoluteURL(src); + this.el_.vjs_src(src); + + // Currently the SWF doesn't autoplay if you load a source later. + // e.g. Load player w/ no source, wait 2s, set src. + if (this.autoplay()) { + var tech = this; + this.setTimeout(function () { + tech.play(); + }, 0); + } + }; + + /** + * Returns true if the tech is currently seeking. + * @return {boolean} true if seeking + */ + + Flash.prototype.seeking = function seeking() { + return this.lastSeekTarget_ !== undefined; + }; + + /** + * Set current time + * + * @param {Number} time Current time of video + * @method setCurrentTime + */ + + Flash.prototype.setCurrentTime = function setCurrentTime(time) { + var seekable = this.seekable(); + if (seekable.length) { + // clamp to the current seekable range + time = time > seekable.start(0) ? time : seekable.start(0); + time = time < seekable.end(seekable.length - 1) ? time : seekable.end(seekable.length - 1); + + this.lastSeekTarget_ = time; + this.trigger('seeking'); + this.el_.vjs_setProperty('currentTime', time); + _Tech.prototype.setCurrentTime.call(this); + } + }; + + /** + * Get current time + * + * @param {Number=} time Current time of video + * @return {Number} Current time + * @method currentTime + */ + + Flash.prototype.currentTime = function currentTime(time) { + // when seeking make the reported time keep up with the requested time + // by reading the time we're seeking to + if (this.seeking()) { + return this.lastSeekTarget_ || 0; + } + return this.el_.vjs_getProperty('currentTime'); + }; + + /** + * Get current source + * + * @method currentSrc + */ + + Flash.prototype.currentSrc = function currentSrc() { + if (this.currentSource_) { + return this.currentSource_.src; + } else { + return this.el_.vjs_getProperty('currentSrc'); + } + }; + + /** + * Load media into player + * + * @method load + */ + + Flash.prototype.load = function load() { + this.el_.vjs_load(); + }; + + /** + * Get poster + * + * @method poster + */ + + Flash.prototype.poster = function poster() { + this.el_.vjs_getProperty('poster'); + }; + + /** + * Poster images are not handled by the Flash tech so make this a no-op + * + * @method setPoster + */ + + Flash.prototype.setPoster = function setPoster() {}; + + /** + * Determine if can seek in media + * + * @return {TimeRangeObject} + * @method seekable + */ + + Flash.prototype.seekable = function seekable() { + var duration = this.duration(); + if (duration === 0) { + return _utilsTimeRangesJs.createTimeRange(); + } + return _utilsTimeRangesJs.createTimeRange(0, duration); + }; + + /** + * Get buffered time range + * + * @return {TimeRangeObject} + * @method buffered + */ + + Flash.prototype.buffered = function buffered() { + var ranges = this.el_.vjs_getProperty('buffered'); + if (ranges.length === 0) { + return _utilsTimeRangesJs.createTimeRange(); + } + return _utilsTimeRangesJs.createTimeRange(ranges[0][0], ranges[0][1]); + }; + + /** + * Get fullscreen support - + * Flash does not allow fullscreen through javascript + * so always returns false + * + * @return {Boolean} false + * @method supportsFullScreen + */ + + Flash.prototype.supportsFullScreen = function supportsFullScreen() { + return false; // Flash does not allow fullscreen through javascript + }; + + /** + * Request to enter fullscreen + * Flash does not allow fullscreen through javascript + * so always returns false + * + * @return {Boolean} false + * @method enterFullScreen + */ + + Flash.prototype.enterFullScreen = function enterFullScreen() { + return false; + }; + + return Flash; +})(_tech2['default']); + +var _api = Flash.prototype; +var _readWrite = 'rtmpConnection,rtmpStream,preload,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(','); +var _readOnly = 'networkState,readyState,initialTime,duration,startOffsetTime,paused,ended,videoWidth,videoHeight'.split(','); + +function _createSetter(attr) { + var attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1); + _api['set' + attrUpper] = function (val) { + return this.el_.vjs_setProperty(attr, val); + }; +} +function _createGetter(attr) { + _api[attr] = function () { + return this.el_.vjs_getProperty(attr); + }; +} + +// Create getter and setters for all read/write attributes +for (var i = 0; i < _readWrite.length; i++) { + _createGetter(_readWrite[i]); + _createSetter(_readWrite[i]); +} + +// Create getters for read-only attributes +for (var i = 0; i < _readOnly.length; i++) { + _createGetter(_readOnly[i]); +} + +/* Flash Support Testing -------------------------------------------------------- */ + +Flash.isSupported = function () { + return Flash.version()[0] >= 10; + // return swfobject.hasFlashPlayerVersion('10'); +}; + +// Add Source Handler pattern functions to this tech +_tech2['default'].withSourceHandlers(Flash); + +/* + * The default native source handler. + * This simply passes the source to the video element. Nothing fancy. + * + * @param {Object} source The source object + * @param {Flash} tech The instance of the Flash tech + */ +Flash.nativeSourceHandler = {}; + +/** + * Check if Flash can play the given videotype + * @param {String} type The mimetype to check + * @return {String} 'probably', 'maybe', or '' (empty string) + */ +Flash.nativeSourceHandler.canPlayType = function (type) { + if (type in Flash.formats) { + return 'maybe'; + } + + return ''; +}; + +/* + * Check Flash can handle the source natively + * + * @param {Object} source The source object + * @param {Object} options The options passed to the tech + * @return {String} 'probably', 'maybe', or '' (empty string) + */ +Flash.nativeSourceHandler.canHandleSource = function (source, options) { + var type; + + function guessMimeType(src) { + var ext = Url.getFileExtension(src); + if (ext) { + return 'video/' + ext; + } + return ''; + } + + if (!source.type) { + type = guessMimeType(source.src); + } else { + // Strip code information from the type because we don't get that specific + type = source.type.replace(/;.*/, '').toLowerCase(); + } + + return Flash.nativeSourceHandler.canPlayType(type); +}; + +/* + * Pass the source to the flash object + * Adaptive source handlers will have more complicated workflows before passing + * video data to the video element + * + * @param {Object} source The source object + * @param {Flash} tech The instance of the Flash tech + * @param {Object} options The options to pass to the source + */ +Flash.nativeSourceHandler.handleSource = function (source, tech, options) { + tech.setSrc(source.src); +}; + +/* + * Clean up the source handler when disposing the player or switching sources.. + * (no cleanup is needed when supporting the format natively) + */ +Flash.nativeSourceHandler.dispose = function () {}; + +// Register the native source handler +Flash.registerSourceHandler(Flash.nativeSourceHandler); + +Flash.formats = { + 'video/flv': 'FLV', + 'video/x-flv': 'FLV', + 'video/mp4': 'MP4', + 'video/m4v': 'MP4' +}; + +Flash.onReady = function (currSwf) { + var el = Dom.getEl(currSwf); + var tech = el && el.tech; + + // if there is no el then the tech has been disposed + // and the tech element was removed from the player div + if (tech && tech.el()) { + // check that the flash object is really ready + Flash.checkReady(tech); + } +}; + +// The SWF isn't always ready when it says it is. Sometimes the API functions still need to be added to the object. +// If it's not ready, we set a timeout to check again shortly. +Flash.checkReady = function (tech) { + // stop worrying if the tech has been disposed + if (!tech.el()) { + return; + } + + // check if API property exists + if (tech.el().vjs_getProperty) { + // tell tech it's ready + tech.triggerReady(); + } else { + // wait longer + this.setTimeout(function () { + Flash['checkReady'](tech); + }, 50); + } +}; + +// Trigger events from the swf on the player +Flash.onEvent = function (swfID, eventName) { + var tech = Dom.getEl(swfID).tech; + tech.trigger(eventName); +}; + +// Log errors from the swf +Flash.onError = function (swfID, err) { + var tech = Dom.getEl(swfID).tech; + + // trigger MEDIA_ERR_SRC_NOT_SUPPORTED + if (err === 'srcnotfound') { + return tech.error(4); + } + + // trigger a custom error + tech.error('FLASH: ' + err); +}; + +// Flash Version Check +Flash.version = function () { + var version = '0,0,0'; + + // IE + try { + version = new _globalWindow2['default'].ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version').replace(/\D+/g, ',').match(/^,?(.+),?$/)[1]; + + // other browsers + } catch (e) { + try { + if (navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin) { + version = (navigator.plugins['Shockwave Flash 2.0'] || navigator.plugins['Shockwave Flash']).description.replace(/\D+/g, ',').match(/^,?(.+),?$/)[1]; + } + } catch (err) {} + } + return version.split(','); +}; + +// Flash embedding method. Only used in non-iframe mode +Flash.embed = function (swf, flashVars, params, attributes) { + var code = Flash.getEmbedCode(swf, flashVars, params, attributes); + + // Get element by embedding code and retrieving created element + var obj = Dom.createEl('div', { innerHTML: code }).childNodes[0]; + + return obj; +}; + +Flash.getEmbedCode = function (swf, flashVars, params, attributes) { + var objTag = ''; + }); + + attributes = _objectAssign2['default']({ + // Add swf to attributes (need both for IE and Others to work) + 'data': swf, + + // Default to 100% width/height + 'width': '100%', + 'height': '100%' + + }, attributes); + + // Create Attributes string + Object.getOwnPropertyNames(attributes).forEach(function (key) { + attrsString += key + '="' + attributes[key] + '" '; + }); + + return '' + objTag + attrsString + '>' + paramsString + ''; +}; + +// Run Flash through the RTMP decorator +_flashRtmp2['default'](Flash); + +_component2['default'].registerComponent('Flash', Flash); +_tech2['default'].registerTech('Flash', Flash); +exports['default'] = Flash; +module.exports = exports['default']; + +},{"../component":67,"../utils/dom.js":142,"../utils/time-ranges.js":150,"../utils/url.js":152,"./flash-rtmp":120,"./tech":124,"global/window":8,"object.assign":53}],122:[function(_dereq_,module,exports){ +/** + * @file html5.js + * HTML5 Media Controller - Wrapper for HTML5 Media API + */ + +'use strict'; + +exports.__esModule = true; + +var _templateObject = _taggedTemplateLiteralLoose(['Text Tracks are being loaded from another origin but the crossorigin attribute isn\'t used. \n This may prevent text tracks from loading.'], ['Text Tracks are being loaded from another origin but the crossorigin attribute isn\'t used. \n This may prevent text tracks from loading.']); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +function _taggedTemplateLiteralLoose(strings, raw) { strings.raw = raw; return strings; } + +var _techJs = _dereq_('./tech.js'); + +var _techJs2 = _interopRequireDefault(_techJs); + +var _component = _dereq_('../component'); + +var _component2 = _interopRequireDefault(_component); + +var _utilsDomJs = _dereq_('../utils/dom.js'); + +var Dom = _interopRequireWildcard(_utilsDomJs); + +var _utilsUrlJs = _dereq_('../utils/url.js'); + +var Url = _interopRequireWildcard(_utilsUrlJs); + +var _utilsFnJs = _dereq_('../utils/fn.js'); + +var Fn = _interopRequireWildcard(_utilsFnJs); + +var _utilsLogJs = _dereq_('../utils/log.js'); + +var _utilsLogJs2 = _interopRequireDefault(_utilsLogJs); + +var _tsml = _dereq_('tsml'); + +var _tsml2 = _interopRequireDefault(_tsml); + +var _srcJsTracksTextTrackJs = _dereq_('../../../src/js/tracks/text-track.js'); + +var _srcJsTracksTextTrackJs2 = _interopRequireDefault(_srcJsTracksTextTrackJs); + +var _utilsBrowserJs = _dereq_('../utils/browser.js'); + +var browser = _interopRequireWildcard(_utilsBrowserJs); + +var _globalDocument = _dereq_('global/document'); + +var _globalDocument2 = _interopRequireDefault(_globalDocument); + +var _globalWindow = _dereq_('global/window'); + +var _globalWindow2 = _interopRequireDefault(_globalWindow); + +var _objectAssign = _dereq_('object.assign'); + +var _objectAssign2 = _interopRequireDefault(_objectAssign); + +var _utilsMergeOptionsJs = _dereq_('../utils/merge-options.js'); + +var _utilsMergeOptionsJs2 = _interopRequireDefault(_utilsMergeOptionsJs); + +var _utilsToTitleCaseJs = _dereq_('../utils/to-title-case.js'); + +var _utilsToTitleCaseJs2 = _interopRequireDefault(_utilsToTitleCaseJs); + +/** + * HTML5 Media Controller - Wrapper for HTML5 Media API + * + * @param {Object=} options Object of option names and values + * @param {Function=} ready Ready callback function + * @extends Tech + * @class Html5 + */ + +var Html5 = (function (_Tech) { + _inherits(Html5, _Tech); + + function Html5(options, ready) { + var _this = this; + + _classCallCheck(this, Html5); + + _Tech.call(this, options, ready); + + var source = options.source; + var crossoriginTracks = false; + + // Set the source if one is provided + // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted) + // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source + // anyway so the error gets fired. + if (source && (this.el_.currentSrc !== source.src || options.tag && options.tag.initNetworkState_ === 3)) { + this.setSource(source); + } else { + this.handleLateInit_(this.el_); + } + + if (this.el_.hasChildNodes()) { + + var nodes = this.el_.childNodes; + var nodesLength = nodes.length; + var removeNodes = []; + + while (nodesLength--) { + var node = nodes[nodesLength]; + var nodeName = node.nodeName.toLowerCase(); + + if (nodeName === 'track') { + if (!this.featuresNativeTextTracks) { + // Empty video tag tracks so the built-in player doesn't use them also. + // This may not be fast enough to stop HTML5 browsers from reading the tags + // so we'll need to turn off any default tracks if we're manually doing + // captions and subtitles. videoElement.textTracks + removeNodes.push(node); + } else { + // store HTMLTrackElement and TextTrack to remote list + this.remoteTextTrackEls().addTrackElement_(node); + this.remoteTextTracks().addTrack_(node.track); + if (!crossoriginTracks && !this.el_.hasAttribute('crossorigin') && Url.isCrossOrigin(node.src)) { + crossoriginTracks = true; + } + } + } + } + + for (var i = 0; i < removeNodes.length; i++) { + this.el_.removeChild(removeNodes[i]); + } + } + + var trackTypes = ['audio', 'video']; + + // ProxyNativeTextTracks + trackTypes.forEach(function (type) { + var capitalType = _utilsToTitleCaseJs2['default'](type); + + if (!_this['featuresNative' + capitalType + 'Tracks']) { + return; + } + var tl = _this.el()[type + 'Tracks']; + + if (tl && tl.addEventListener) { + tl.addEventListener('change', Fn.bind(_this, _this['handle' + capitalType + 'TrackChange_'])); + tl.addEventListener('addtrack', Fn.bind(_this, _this['handle' + capitalType + 'TrackAdd_'])); + tl.addEventListener('removetrack', Fn.bind(_this, _this['handle' + capitalType + 'TrackRemove_'])); + + // Remove (native) trackts that are not used anymore + _this.on('loadstart', _this['removeOld' + capitalType + 'Tracks_']); + } + }); + + if (this.featuresNativeTextTracks) { + if (crossoriginTracks) { + _utilsLogJs2['default'].warn(_tsml2['default'](_templateObject)); + } + + this.handleTextTrackChange_ = Fn.bind(this, this.handleTextTrackChange); + this.handleTextTrackAdd_ = Fn.bind(this, this.handleTextTrackAdd); + this.handleTextTrackRemove_ = Fn.bind(this, this.handleTextTrackRemove); + this.proxyNativeTextTracks_(); + } + + // Determine if native controls should be used + // Our goal should be to get the custom controls on mobile solid everywhere + // so we can remove this all together. Right now this will block custom + // controls on touch enabled laptops like the Chrome Pixel + if (browser.TOUCH_ENABLED && options.nativeControlsForTouch === true || browser.IS_IPHONE || browser.IS_NATIVE_ANDROID) { + this.setControls(true); + } + + this.triggerReady(); + } + + /* HTML5 Support Testing ---------------------------------------------------- */ + + /* + * Element for testing browser HTML5 video capabilities + * + * @type {Element} + * @constant + * @private + */ + + /** + * Dispose of html5 media element + * + * @method dispose + */ + + Html5.prototype.dispose = function dispose() { + var _this2 = this; + + // Un-ProxyNativeTracks + ['audio', 'video', 'text'].forEach(function (type) { + var capitalType = _utilsToTitleCaseJs2['default'](type); + var tl = _this2.el_[type + 'Tracks']; + + if (tl && tl.removeEventListener) { + tl.removeEventListener('change', _this2['handle' + capitalType + 'TrackChange_']); + tl.removeEventListener('addtrack', _this2['handle' + capitalType + 'TrackAdd_']); + tl.removeEventListener('removetrack', _this2['handle' + capitalType + 'TrackRemove_']); + } + + // Stop removing old text tracks + if (tl) { + _this2.off('loadstart', _this2['removeOld' + capitalType + 'Tracks_']); + } + }); + + Html5.disposeMediaElement(this.el_); + // tech will handle clearing of the emulated track list + _Tech.prototype.dispose.call(this); + }; + + /** + * Create the component's DOM element + * + * @return {Element} + * @method createEl + */ + + Html5.prototype.createEl = function createEl() { + var el = this.options_.tag; + + // Check if this browser supports moving the element into the box. + // On the iPhone video will break if you move the element, + // So we have to create a brand new element. + if (!el || this['movingMediaElementInDOM'] === false) { + + // If the original tag is still there, clone and remove it. + if (el) { + var clone = el.cloneNode(true); + el.parentNode.insertBefore(clone, el); + Html5.disposeMediaElement(el); + el = clone; + } else { + el = _globalDocument2['default'].createElement('video'); + + // determine if native controls should be used + var tagAttributes = this.options_.tag && Dom.getElAttributes(this.options_.tag); + var attributes = _utilsMergeOptionsJs2['default']({}, tagAttributes); + if (!browser.TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) { + delete attributes.controls; + } + + Dom.setElAttributes(el, _objectAssign2['default'](attributes, { + id: this.options_.techId, + 'class': 'vjs-tech' + })); + } + } + + // Update specific tag settings, in case they were overridden + var settingsAttrs = ['autoplay', 'preload', 'loop', 'muted']; + for (var i = settingsAttrs.length - 1; i >= 0; i--) { + var attr = settingsAttrs[i]; + var overwriteAttrs = {}; + if (typeof this.options_[attr] !== 'undefined') { + overwriteAttrs[attr] = this.options_[attr]; + } + Dom.setElAttributes(el, overwriteAttrs); + } + + return el; + // jenniisawesome = true; + }; + + // If we're loading the playback object after it has started loading + // or playing the video (often with autoplay on) then the loadstart event + // has already fired and we need to fire it manually because many things + // rely on it. + + Html5.prototype.handleLateInit_ = function handleLateInit_(el) { + var _this3 = this; + + if (el.networkState === 0 || el.networkState === 3) { + // The video element hasn't started loading the source yet + // or didn't find a source + return; + } + + if (el.readyState === 0) { + var _ret = (function () { + // NetworkState is set synchronously BUT loadstart is fired at the + // end of the current stack, usually before setInterval(fn, 0). + // So at this point we know loadstart may have already fired or is + // about to fire, and either way the player hasn't seen it yet. + // We don't want to fire loadstart prematurely here and cause a + // double loadstart so we'll wait and see if it happens between now + // and the next loop, and fire it if not. + // HOWEVER, we also want to make sure it fires before loadedmetadata + // which could also happen between now and the next loop, so we'll + // watch for that also. + var loadstartFired = false; + var setLoadstartFired = function setLoadstartFired() { + loadstartFired = true; + }; + _this3.on('loadstart', setLoadstartFired); + + var triggerLoadstart = function triggerLoadstart() { + // We did miss the original loadstart. Make sure the player + // sees loadstart before loadedmetadata + if (!loadstartFired) { + this.trigger('loadstart'); + } + }; + _this3.on('loadedmetadata', triggerLoadstart); + + _this3.ready(function () { + this.off('loadstart', setLoadstartFired); + this.off('loadedmetadata', triggerLoadstart); + + if (!loadstartFired) { + // We did miss the original native loadstart. Fire it now. + this.trigger('loadstart'); + } + }); + + return { + v: undefined + }; + })(); + + if (typeof _ret === 'object') return _ret.v; + } + + // From here on we know that loadstart already fired and we missed it. + // The other readyState events aren't as much of a problem if we double + // them, so not going to go to as much trouble as loadstart to prevent + // that unless we find reason to. + var eventsToTrigger = ['loadstart']; + + // loadedmetadata: newly equal to HAVE_METADATA (1) or greater + eventsToTrigger.push('loadedmetadata'); + + // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater + if (el.readyState >= 2) { + eventsToTrigger.push('loadeddata'); + } + + // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater + if (el.readyState >= 3) { + eventsToTrigger.push('canplay'); + } + + // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4) + if (el.readyState >= 4) { + eventsToTrigger.push('canplaythrough'); + } + + // We still need to give the player time to add event listeners + this.ready(function () { + eventsToTrigger.forEach(function (type) { + this.trigger(type); + }, this); + }); + }; + + Html5.prototype.proxyNativeTextTracks_ = function proxyNativeTextTracks_() { + var tt = this.el().textTracks; + + if (tt) { + // Add tracks - if player is initialised after DOM loaded, textTracks + // will not trigger addtrack + for (var i = 0; i < tt.length; i++) { + this.textTracks().addTrack_(tt[i]); + } + + if (tt.addEventListener) { + tt.addEventListener('change', this.handleTextTrackChange_); + tt.addEventListener('addtrack', this.handleTextTrackAdd_); + tt.addEventListener('removetrack', this.handleTextTrackRemove_); + } + + // Remove (native) texttracks that are not used anymore + this.on('loadstart', this.removeOldTextTracks_); + } + }; + + Html5.prototype.handleTextTrackChange = function handleTextTrackChange(e) { + var tt = this.textTracks(); + this.textTracks().trigger({ + type: 'change', + target: tt, + currentTarget: tt, + srcElement: tt + }); + }; + + Html5.prototype.handleTextTrackAdd = function handleTextTrackAdd(e) { + this.textTracks().addTrack_(e.track); + }; + + Html5.prototype.handleTextTrackRemove = function handleTextTrackRemove(e) { + this.textTracks().removeTrack_(e.track); + }; + + Html5.prototype.handleVideoTrackChange_ = function handleVideoTrackChange_(e) { + var vt = this.videoTracks(); + this.videoTracks().trigger({ + type: 'change', + target: vt, + currentTarget: vt, + srcElement: vt + }); + }; + + Html5.prototype.handleVideoTrackAdd_ = function handleVideoTrackAdd_(e) { + this.videoTracks().addTrack_(e.track); + }; + + Html5.prototype.handleVideoTrackRemove_ = function handleVideoTrackRemove_(e) { + this.videoTracks().removeTrack_(e.track); + }; + + Html5.prototype.handleAudioTrackChange_ = function handleAudioTrackChange_(e) { + var audioTrackList = this.audioTracks(); + this.audioTracks().trigger({ + type: 'change', + target: audioTrackList, + currentTarget: audioTrackList, + srcElement: audioTrackList + }); + }; + + Html5.prototype.handleAudioTrackAdd_ = function handleAudioTrackAdd_(e) { + this.audioTracks().addTrack_(e.track); + }; + + Html5.prototype.handleAudioTrackRemove_ = function handleAudioTrackRemove_(e) { + this.audioTracks().removeTrack_(e.track); + }; + + /** + * This is a helper function that is used in removeOldTextTracks_, removeOldAudioTracks_ and + * removeOldVideoTracks_ + * @param {Track[]} techTracks Tracks for this tech + * @param {Track[]} elTracks Tracks for the HTML5 video element + * @private + */ + + Html5.prototype.removeOldTracks_ = function removeOldTracks_(techTracks, elTracks) { + // This will loop over the techTracks and check if they are still used by the HTML5 video element + // If not, they will be removed from the emulated list + var removeTracks = []; + if (!elTracks) { + return; + } + + for (var i = 0; i < techTracks.length; i++) { + var techTrack = techTracks[i]; + + var found = false; + for (var j = 0; j < elTracks.length; j++) { + if (elTracks[j] === techTrack) { + found = true; + break; + } + } + + if (!found) { + removeTracks.push(techTrack); + } + } + + for (var i = 0; i < removeTracks.length; i++) { + var _track = removeTracks[i]; + techTracks.removeTrack_(_track); + } + }; + + Html5.prototype.removeOldTextTracks_ = function removeOldTextTracks_() { + var techTracks = this.textTracks(); + var elTracks = this.el().textTracks; + this.removeOldTracks_(techTracks, elTracks); + }; + + Html5.prototype.removeOldAudioTracks_ = function removeOldAudioTracks_() { + var techTracks = this.audioTracks(); + var elTracks = this.el().audioTracks; + this.removeOldTracks_(techTracks, elTracks); + }; + + Html5.prototype.removeOldVideoTracks_ = function removeOldVideoTracks_() { + var techTracks = this.videoTracks(); + var elTracks = this.el().videoTracks; + this.removeOldTracks_(techTracks, elTracks); + }; + + /** + * Play for html5 tech + * + * @method play + */ + + Html5.prototype.play = function play() { + this.el_.play(); + }; + + /** + * Pause for html5 tech + * + * @method pause + */ + + Html5.prototype.pause = function pause() { + this.el_.pause(); + }; + + /** + * Paused for html5 tech + * + * @return {Boolean} + * @method paused + */ + + Html5.prototype.paused = function paused() { + return this.el_.paused; + }; + + /** + * Get current time + * + * @return {Number} + * @method currentTime + */ + + Html5.prototype.currentTime = function currentTime() { + return this.el_.currentTime; + }; + + /** + * Set current time + * + * @param {Number} seconds Current time of video + * @method setCurrentTime + */ + + Html5.prototype.setCurrentTime = function setCurrentTime(seconds) { + try { + this.el_.currentTime = seconds; + } catch (e) { + _utilsLogJs2['default'](e, 'Video is not ready. (Video.js)'); + // this.warning(VideoJS.warnings.videoNotReady); + } + }; + + /** + * Get duration + * + * @return {Number} + * @method duration + */ + + Html5.prototype.duration = function duration() { + return this.el_.duration || 0; + }; + + /** + * Get a TimeRange object that represents the intersection + * of the time ranges for which the user agent has all + * relevant media + * + * @return {TimeRangeObject} + * @method buffered + */ + + Html5.prototype.buffered = function buffered() { + return this.el_.buffered; + }; + + /** + * Get volume level + * + * @return {Number} + * @method volume + */ + + Html5.prototype.volume = function volume() { + return this.el_.volume; + }; + + /** + * Set volume level + * + * @param {Number} percentAsDecimal Volume percent as a decimal + * @method setVolume + */ + + Html5.prototype.setVolume = function setVolume(percentAsDecimal) { + this.el_.volume = percentAsDecimal; + }; + + /** + * Get if muted + * + * @return {Boolean} + * @method muted + */ + + Html5.prototype.muted = function muted() { + return this.el_.muted; + }; + + /** + * Set muted + * + * @param {Boolean} If player is to be muted or note + * @method setMuted + */ + + Html5.prototype.setMuted = function setMuted(muted) { + this.el_.muted = muted; + }; + + /** + * Get player width + * + * @return {Number} + * @method width + */ + + Html5.prototype.width = function width() { + return this.el_.offsetWidth; + }; + + /** + * Get player height + * + * @return {Number} + * @method height + */ + + Html5.prototype.height = function height() { + return this.el_.offsetHeight; + }; + + /** + * Get if there is fullscreen support + * + * @return {Boolean} + * @method supportsFullScreen + */ + + Html5.prototype.supportsFullScreen = function supportsFullScreen() { + if (typeof this.el_.webkitEnterFullScreen === 'function') { + var userAgent = _globalWindow2['default'].navigator.userAgent; + // Seems to be broken in Chromium/Chrome && Safari in Leopard + if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) { + return true; + } + } + return false; + }; + + /** + * Request to enter fullscreen + * + * @method enterFullScreen + */ + + Html5.prototype.enterFullScreen = function enterFullScreen() { + var video = this.el_; + + if ('webkitDisplayingFullscreen' in video) { + this.one('webkitbeginfullscreen', function () { + this.one('webkitendfullscreen', function () { + this.trigger('fullscreenchange', { isFullscreen: false }); + }); + + this.trigger('fullscreenchange', { isFullscreen: true }); + }); + } + + if (video.paused && video.networkState <= video.HAVE_METADATA) { + // attempt to prime the video element for programmatic access + // this isn't necessary on the desktop but shouldn't hurt + this.el_.play(); + + // playing and pausing synchronously during the transition to fullscreen + // can get iOS ~6.1 devices into a play/pause loop + this.setTimeout(function () { + video.pause(); + video.webkitEnterFullScreen(); + }, 0); + } else { + video.webkitEnterFullScreen(); + } + }; + + /** + * Request to exit fullscreen + * + * @method exitFullScreen + */ + + Html5.prototype.exitFullScreen = function exitFullScreen() { + this.el_.webkitExitFullScreen(); + }; + + /** + * Get/set video + * + * @param {Object=} src Source object + * @return {Object} + * @method src + */ + + Html5.prototype.src = function src(_src) { + if (_src === undefined) { + return this.el_.src; + } else { + // Setting src through `src` instead of `setSrc` will be deprecated + this.setSrc(_src); + } + }; + + /** + * Set video + * + * @param {Object} src Source object + * @deprecated + * @method setSrc + */ + + Html5.prototype.setSrc = function setSrc(src) { + this.el_.src = src; + }; + + /** + * Load media into player + * + * @method load + */ + + Html5.prototype.load = function load() { + this.el_.load(); + }; + + /** + * Reset the tech. Removes all sources and calls `load`. + * + * @method reset + */ + + Html5.prototype.reset = function reset() { + Html5.resetMediaElement(this.el_); + }; + + /** + * Get current source + * + * @return {Object} + * @method currentSrc + */ + + Html5.prototype.currentSrc = function currentSrc() { + if (this.currentSource_) { + return this.currentSource_.src; + } else { + return this.el_.currentSrc; + } + }; + + /** + * Get poster + * + * @return {String} + * @method poster + */ + + Html5.prototype.poster = function poster() { + return this.el_.poster; + }; + + /** + * Set poster + * + * @param {String} val URL to poster image + * @method + */ + + Html5.prototype.setPoster = function setPoster(val) { + this.el_.poster = val; + }; + + /** + * Get preload attribute + * + * @return {String} + * @method preload + */ + + Html5.prototype.preload = function preload() { + return this.el_.preload; + }; + + /** + * Set preload attribute + * + * @param {String} val Value for preload attribute + * @method setPreload + */ + + Html5.prototype.setPreload = function setPreload(val) { + this.el_.preload = val; + }; + + /** + * Get autoplay attribute + * + * @return {String} + * @method autoplay + */ + + Html5.prototype.autoplay = function autoplay() { + return this.el_.autoplay; + }; + + /** + * Set autoplay attribute + * + * @param {String} val Value for preload attribute + * @method setAutoplay + */ + + Html5.prototype.setAutoplay = function setAutoplay(val) { + this.el_.autoplay = val; + }; + + /** + * Get controls attribute + * + * @return {String} + * @method controls + */ + + Html5.prototype.controls = function controls() { + return this.el_.controls; + }; + + /** + * Set controls attribute + * + * @param {String} val Value for controls attribute + * @method setControls + */ + + Html5.prototype.setControls = function setControls(val) { + this.el_.controls = !!val; + }; + + /** + * Get loop attribute + * + * @return {String} + * @method loop + */ + + Html5.prototype.loop = function loop() { + return this.el_.loop; + }; + + /** + * Set loop attribute + * + * @param {String} val Value for loop attribute + * @method setLoop + */ + + Html5.prototype.setLoop = function setLoop(val) { + this.el_.loop = val; + }; + + /** + * Get error value + * + * @return {String} + * @method error + */ + + Html5.prototype.error = function error() { + return this.el_.error; + }; + + /** + * Get whether or not the player is in the "seeking" state + * + * @return {Boolean} + * @method seeking + */ + + Html5.prototype.seeking = function seeking() { + return this.el_.seeking; + }; + + /** + * Get a TimeRanges object that represents the + * ranges of the media resource to which it is possible + * for the user agent to seek. + * + * @return {TimeRangeObject} + * @method seekable + */ + + Html5.prototype.seekable = function seekable() { + return this.el_.seekable; + }; + + /** + * Get if video ended + * + * @return {Boolean} + * @method ended + */ + + Html5.prototype.ended = function ended() { + return this.el_.ended; + }; + + /** + * Get the value of the muted content attribute + * This attribute has no dynamic effect, it only + * controls the default state of the element + * + * @return {Boolean} + * @method defaultMuted + */ + + Html5.prototype.defaultMuted = function defaultMuted() { + return this.el_.defaultMuted; + }; + + /** + * Get desired speed at which the media resource is to play + * + * @return {Number} + * @method playbackRate + */ + + Html5.prototype.playbackRate = function playbackRate() { + return this.el_.playbackRate; + }; + + /** + * Returns a TimeRanges object that represents the ranges of the + * media resource that the user agent has played. + * @return {TimeRangeObject} the range of points on the media + * timeline that has been reached through normal playback + * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-played + */ + + Html5.prototype.played = function played() { + return this.el_.played; + }; + + /** + * Set desired speed at which the media resource is to play + * + * @param {Number} val Speed at which the media resource is to play + * @method setPlaybackRate + */ + + Html5.prototype.setPlaybackRate = function setPlaybackRate(val) { + this.el_.playbackRate = val; + }; + + /** + * Get the current state of network activity for the element, from + * the list below + * NETWORK_EMPTY (numeric value 0) + * NETWORK_IDLE (numeric value 1) + * NETWORK_LOADING (numeric value 2) + * NETWORK_NO_SOURCE (numeric value 3) + * + * @return {Number} + * @method networkState + */ + + Html5.prototype.networkState = function networkState() { + return this.el_.networkState; + }; + + /** + * Get a value that expresses the current state of the element + * with respect to rendering the current playback position, from + * the codes in the list below + * HAVE_NOTHING (numeric value 0) + * HAVE_METADATA (numeric value 1) + * HAVE_CURRENT_DATA (numeric value 2) + * HAVE_FUTURE_DATA (numeric value 3) + * HAVE_ENOUGH_DATA (numeric value 4) + * + * @return {Number} + * @method readyState + */ + + Html5.prototype.readyState = function readyState() { + return this.el_.readyState; + }; + + /** + * Get width of video + * + * @return {Number} + * @method videoWidth + */ + + Html5.prototype.videoWidth = function videoWidth() { + return this.el_.videoWidth; + }; + + /** + * Get height of video + * + * @return {Number} + * @method videoHeight + */ + + Html5.prototype.videoHeight = function videoHeight() { + return this.el_.videoHeight; + }; + + /** + * Get text tracks + * + * @return {TextTrackList} + * @method textTracks + */ + + Html5.prototype.textTracks = function textTracks() { + return _Tech.prototype.textTracks.call(this); + }; + + /** + * Creates and returns a text track object + * + * @param {String} kind Text track kind (subtitles, captions, descriptions + * chapters and metadata) + * @param {String=} label Label to identify the text track + * @param {String=} language Two letter language abbreviation + * @return {TextTrackObject} + * @method addTextTrack + */ + + Html5.prototype.addTextTrack = function addTextTrack(kind, label, language) { + if (!this['featuresNativeTextTracks']) { + return _Tech.prototype.addTextTrack.call(this, kind, label, language); + } + + return this.el_.addTextTrack(kind, label, language); + }; + + /** + * Creates a remote text track object and returns a html track element + * + * @param {Object} options The object should contain values for + * kind, language, label and src (location of the WebVTT file) + * @return {HTMLTrackElement} + * @method addRemoteTextTrack + */ + + Html5.prototype.addRemoteTextTrack = function addRemoteTextTrack() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + if (!this['featuresNativeTextTracks']) { + return _Tech.prototype.addRemoteTextTrack.call(this, options); + } + + var htmlTrackElement = _globalDocument2['default'].createElement('track'); + + if (options.kind) { + htmlTrackElement.kind = options.kind; + } + if (options.label) { + htmlTrackElement.label = options.label; + } + if (options.language || options.srclang) { + htmlTrackElement.srclang = options.language || options.srclang; + } + if (options['default']) { + htmlTrackElement['default'] = options['default']; + } + if (options.id) { + htmlTrackElement.id = options.id; + } + if (options.src) { + htmlTrackElement.src = options.src; + } + + this.el().appendChild(htmlTrackElement); + + // store HTMLTrackElement and TextTrack to remote list + this.remoteTextTrackEls().addTrackElement_(htmlTrackElement); + this.remoteTextTracks().addTrack_(htmlTrackElement.track); + + return htmlTrackElement; + }; + + /** + * Remove remote text track from TextTrackList object + * + * @param {TextTrackObject} track Texttrack object to remove + * @method removeRemoteTextTrack + */ + + Html5.prototype.removeRemoteTextTrack = function removeRemoteTextTrack(track) { + if (!this['featuresNativeTextTracks']) { + return _Tech.prototype.removeRemoteTextTrack.call(this, track); + } + + var tracks = undefined, + i = undefined; + + var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); + + // remove HTMLTrackElement and TextTrack from remote list + this.remoteTextTrackEls().removeTrackElement_(trackElement); + this.remoteTextTracks().removeTrack_(track); + + tracks = this.$$('track'); + + i = tracks.length; + while (i--) { + if (track === tracks[i] || track === tracks[i].track) { + this.el().removeChild(tracks[i]); + } + } + }; + + return Html5; +})(_techJs2['default']); + +Html5.TEST_VID = _globalDocument2['default'].createElement('video'); +var track = _globalDocument2['default'].createElement('track'); +track.kind = 'captions'; +track.srclang = 'en'; +track.label = 'English'; +Html5.TEST_VID.appendChild(track); + +/* + * Check if HTML5 video is supported by this browser/device + * + * @return {Boolean} + */ +Html5.isSupported = function () { + // IE9 with no Media Player is a LIAR! (#984) + try { + Html5.TEST_VID['volume'] = 0.5; + } catch (e) { + return false; + } + + return !!Html5.TEST_VID.canPlayType; +}; + +// Add Source Handler pattern functions to this tech +_techJs2['default'].withSourceHandlers(Html5); + +/* + * The default native source handler. + * This simply passes the source to the video element. Nothing fancy. + * + * @param {Object} source The source object + * @param {Html5} tech The instance of the HTML5 tech + */ +Html5.nativeSourceHandler = {}; + +/* + * Check if the video element can play the given videotype + * + * @param {String} type The mimetype to check + * @return {String} 'probably', 'maybe', or '' (empty string) + */ +Html5.nativeSourceHandler.canPlayType = function (type) { + // IE9 on Windows 7 without MediaPlayer throws an error here + // https://github.com/videojs/video.js/issues/519 + try { + return Html5.TEST_VID.canPlayType(type); + } catch (e) { + return ''; + } +}; + +/* + * Check if the video element can handle the source natively + * + * @param {Object} source The source object + * @param {Object} options The options passed to the tech + * @return {String} 'probably', 'maybe', or '' (empty string) + */ +Html5.nativeSourceHandler.canHandleSource = function (source, options) { + var match, ext; + + // If a type was provided we should rely on that + if (source.type) { + return Html5.nativeSourceHandler.canPlayType(source.type); + } else if (source.src) { + // If no type, fall back to checking 'video/[EXTENSION]' + ext = Url.getFileExtension(source.src); + + return Html5.nativeSourceHandler.canPlayType('video/' + ext); + } + + return ''; +}; + +/* + * Pass the source to the video element + * Adaptive source handlers will have more complicated workflows before passing + * video data to the video element + * + * @param {Object} source The source object + * @param {Html5} tech The instance of the Html5 tech + * @param {Object} options The options to pass to the source + */ +Html5.nativeSourceHandler.handleSource = function (source, tech, options) { + tech.setSrc(source.src); +}; + +/* +* Clean up the source handler when disposing the player or switching sources.. +* (no cleanup is needed when supporting the format natively) +*/ +Html5.nativeSourceHandler.dispose = function () {}; + +// Register the native source handler +Html5.registerSourceHandler(Html5.nativeSourceHandler); + +/* + * Check if the volume can be changed in this browser/device. + * Volume cannot be changed in a lot of mobile devices. + * Specifically, it can't be changed from 1 on iOS. + * + * @return {Boolean} + */ +Html5.canControlVolume = function () { + // IE will error if Windows Media Player not installed #3315 + try { + var volume = Html5.TEST_VID.volume; + Html5.TEST_VID.volume = volume / 2 + 0.1; + return volume !== Html5.TEST_VID.volume; + } catch (e) { + return false; + } +}; + +/* + * Check if playbackRate is supported in this browser/device. + * + * @return {Boolean} + */ +Html5.canControlPlaybackRate = function () { + // Playback rate API is implemented in Android Chrome, but doesn't do anything + // https://github.com/videojs/video.js/issues/3180 + if (browser.IS_ANDROID && browser.IS_CHROME) { + return false; + } + // IE will error if Windows Media Player not installed #3315 + try { + var playbackRate = Html5.TEST_VID.playbackRate; + Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1; + return playbackRate !== Html5.TEST_VID.playbackRate; + } catch (e) { + return false; + } +}; + +/* + * Check to see if native text tracks are supported by this browser/device + * + * @return {Boolean} + */ +Html5.supportsNativeTextTracks = function () { + var supportsTextTracks; + + // Figure out native text track support + // If mode is a number, we cannot change it because it'll disappear from view. + // Browsers with numeric modes include IE10 and older (<=2013) samsung android models. + // Firefox isn't playing nice either with modifying the mode + // TODO: Investigate firefox: https://github.com/videojs/video.js/issues/1862 + supportsTextTracks = !!Html5.TEST_VID.textTracks; + if (supportsTextTracks && Html5.TEST_VID.textTracks.length > 0) { + supportsTextTracks = typeof Html5.TEST_VID.textTracks[0]['mode'] !== 'number'; + } + if (supportsTextTracks && browser.IS_FIREFOX) { + supportsTextTracks = false; + } + if (supportsTextTracks && !('onremovetrack' in Html5.TEST_VID.textTracks)) { + supportsTextTracks = false; + } + + return supportsTextTracks; +}; + +/* + * Check to see if native video tracks are supported by this browser/device + * + * @return {Boolean} + */ +Html5.supportsNativeVideoTracks = function () { + var supportsVideoTracks = !!Html5.TEST_VID.videoTracks; + return supportsVideoTracks; +}; + +/* + * Check to see if native audio tracks are supported by this browser/device + * + * @return {Boolean} + */ +Html5.supportsNativeAudioTracks = function () { + var supportsAudioTracks = !!Html5.TEST_VID.audioTracks; + return supportsAudioTracks; +}; + +/** + * An array of events available on the Html5 tech. + * + * @private + * @type {Array} + */ +Html5.Events = ['loadstart', 'suspend', 'abort', 'error', 'emptied', 'stalled', 'loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough', 'playing', 'waiting', 'seeking', 'seeked', 'ended', 'durationchange', 'timeupdate', 'progress', 'play', 'pause', 'ratechange', 'volumechange']; + +/* + * Set the tech's volume control support status + * + * @type {Boolean} + */ +Html5.prototype['featuresVolumeControl'] = Html5.canControlVolume(); + +/* + * Set the tech's playbackRate support status + * + * @type {Boolean} + */ +Html5.prototype['featuresPlaybackRate'] = Html5.canControlPlaybackRate(); + +/* + * Set the tech's status on moving the video element. + * In iOS, if you move a video element in the DOM, it breaks video playback. + * + * @type {Boolean} + */ +Html5.prototype['movingMediaElementInDOM'] = !browser.IS_IOS; + +/* + * Set the the tech's fullscreen resize support status. + * HTML video is able to automatically resize when going to fullscreen. + * (No longer appears to be used. Can probably be removed.) + */ +Html5.prototype['featuresFullscreenResize'] = true; + +/* + * Set the tech's progress event support status + * (this disables the manual progress events of the Tech) + */ +Html5.prototype['featuresProgressEvents'] = true; + +/* + * Sets the tech's status on native text track support + * + * @type {Boolean} + */ +Html5.prototype['featuresNativeTextTracks'] = Html5.supportsNativeTextTracks(); + +/** + * Sets the tech's status on native text track support + * + * @type {Boolean} + */ +Html5.prototype['featuresNativeVideoTracks'] = Html5.supportsNativeVideoTracks(); + +/** + * Sets the tech's status on native audio track support + * + * @type {Boolean} + */ +Html5.prototype['featuresNativeAudioTracks'] = Html5.supportsNativeAudioTracks(); + +// HTML5 Feature detection and Device Fixes --------------------------------- // +var canPlayType = undefined; +var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i; +var mp4RE = /^video\/mp4/i; + +Html5.patchCanPlayType = function () { + // Android 4.0 and above can play HLS to some extent but it reports being unable to do so + if (browser.ANDROID_VERSION >= 4.0) { + if (!canPlayType) { + canPlayType = Html5.TEST_VID.constructor.prototype.canPlayType; + } + + Html5.TEST_VID.constructor.prototype.canPlayType = function (type) { + if (type && mpegurlRE.test(type)) { + return 'maybe'; + } + return canPlayType.call(this, type); + }; + } + + // Override Android 2.2 and less canPlayType method which is broken + if (browser.IS_OLD_ANDROID) { + if (!canPlayType) { + canPlayType = Html5.TEST_VID.constructor.prototype.canPlayType; + } + + Html5.TEST_VID.constructor.prototype.canPlayType = function (type) { + if (type && mp4RE.test(type)) { + return 'maybe'; + } + return canPlayType.call(this, type); + }; + } +}; + +Html5.unpatchCanPlayType = function () { + var r = Html5.TEST_VID.constructor.prototype.canPlayType; + Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType; + canPlayType = null; + return r; +}; + +// by default, patch the video element +Html5.patchCanPlayType(); + +Html5.disposeMediaElement = function (el) { + if (!el) { + return; + } + + if (el.parentNode) { + el.parentNode.removeChild(el); + } + + // remove any child track or source nodes to prevent their loading + while (el.hasChildNodes()) { + el.removeChild(el.firstChild); + } + + // remove any src reference. not setting `src=''` because that causes a warning + // in firefox + el.removeAttribute('src'); + + // force the media element to update its loading state by calling load() + // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793) + if (typeof el.load === 'function') { + // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473) + (function () { + try { + el.load(); + } catch (e) { + // not supported + } + })(); + } +}; + +Html5.resetMediaElement = function (el) { + if (!el) { + return; + } + + var sources = el.querySelectorAll('source'); + var i = sources.length; + while (i--) { + el.removeChild(sources[i]); + } + + // remove any src reference. + // not setting `src=''` because that throws an error + el.removeAttribute('src'); + + if (typeof el.load === 'function') { + // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473) + (function () { + try { + el.load(); + } catch (e) {} + })(); + } +}; + +_component2['default'].registerComponent('Html5', Html5); +_techJs2['default'].registerTech('Html5', Html5); +exports['default'] = Html5; +module.exports = exports['default']; + +},{"../../../src/js/tracks/text-track.js":134,"../component":67,"../utils/browser.js":140,"../utils/dom.js":142,"../utils/fn.js":144,"../utils/log.js":147,"../utils/merge-options.js":148,"../utils/to-title-case.js":151,"../utils/url.js":152,"./tech.js":124,"global/document":7,"global/window":8,"object.assign":53,"tsml":60}],123:[function(_dereq_,module,exports){ +/** + * @file loader.js + */ +'use strict'; + +exports.__esModule = true; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _componentJs = _dereq_('../component.js'); + +var _componentJs2 = _interopRequireDefault(_componentJs); + +var _techJs = _dereq_('./tech.js'); + +var _techJs2 = _interopRequireDefault(_techJs); + +var _globalWindow = _dereq_('global/window'); + +var _globalWindow2 = _interopRequireDefault(_globalWindow); + +var _utilsToTitleCaseJs = _dereq_('../utils/to-title-case.js'); + +var _utilsToTitleCaseJs2 = _interopRequireDefault(_utilsToTitleCaseJs); + +/** + * The Media Loader is the component that decides which playback technology to load + * when the player is initialized. + * + * @param {Object} player Main Player + * @param {Object=} options Object of option names and values + * @param {Function=} ready Ready callback function + * @extends Component + * @class MediaLoader + */ + +var MediaLoader = (function (_Component) { + _inherits(MediaLoader, _Component); + + function MediaLoader(player, options, ready) { + _classCallCheck(this, MediaLoader); + + _Component.call(this, player, options, ready); + + // If there are no sources when the player is initialized, + // load the first supported playback technology. + + if (!options.playerOptions['sources'] || options.playerOptions['sources'].length === 0) { + for (var i = 0, j = options.playerOptions['techOrder']; i < j.length; i++) { + var techName = _utilsToTitleCaseJs2['default'](j[i]); + var tech = _techJs2['default'].getTech(techName); + // Support old behavior of techs being registered as components. + // Remove once that deprecated behavior is removed. + if (!techName) { + tech = _componentJs2['default'].getComponent(techName); + } + + // Check if the browser supports this technology + if (tech && tech.isSupported()) { + player.loadTech_(techName); + break; + } + } + } else { + // // Loop through playback technologies (HTML5, Flash) and check for support. + // // Then load the best source. + // // A few assumptions here: + // // All playback technologies respect preload false. + player.src(options.playerOptions['sources']); + } + } + + return MediaLoader; +})(_componentJs2['default']); + +_componentJs2['default'].registerComponent('MediaLoader', MediaLoader); +exports['default'] = MediaLoader; +module.exports = exports['default']; + +},{"../component.js":67,"../utils/to-title-case.js":151,"./tech.js":124,"global/window":8}],124:[function(_dereq_,module,exports){ +/** + * @file tech.js + * Media Technology Controller - Base class for media playback + * technology controllers like Flash and HTML5 + */ + +'use strict'; + +exports.__esModule = true; + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _component = _dereq_('../component'); + +var _component2 = _interopRequireDefault(_component); + +var _tracksHtmlTrackElement = _dereq_('../tracks/html-track-element'); + +var _tracksHtmlTrackElement2 = _interopRequireDefault(_tracksHtmlTrackElement); + +var _tracksHtmlTrackElementList = _dereq_('../tracks/html-track-element-list'); + +var _tracksHtmlTrackElementList2 = _interopRequireDefault(_tracksHtmlTrackElementList); + +var _utilsMergeOptionsJs = _dereq_('../utils/merge-options.js'); + +var _utilsMergeOptionsJs2 = _interopRequireDefault(_utilsMergeOptionsJs); + +var _tracksTextTrack = _dereq_('../tracks/text-track'); + +var _tracksTextTrack2 = _interopRequireDefault(_tracksTextTrack); + +var _tracksTextTrackList = _dereq_('../tracks/text-track-list'); + +var _tracksTextTrackList2 = _interopRequireDefault(_tracksTextTrackList); + +var _tracksVideoTrack = _dereq_('../tracks/video-track'); + +var _tracksVideoTrack2 = _interopRequireDefault(_tracksVideoTrack); + +var _tracksVideoTrackList = _dereq_('../tracks/video-track-list'); + +var _tracksVideoTrackList2 = _interopRequireDefault(_tracksVideoTrackList); + +var _tracksAudioTrackList = _dereq_('../tracks/audio-track-list'); + +var _tracksAudioTrackList2 = _interopRequireDefault(_tracksAudioTrackList); + +var _tracksAudioTrack = _dereq_('../tracks/audio-track'); + +var _tracksAudioTrack2 = _interopRequireDefault(_tracksAudioTrack); + +var _utilsFnJs = _dereq_('../utils/fn.js'); + +var Fn = _interopRequireWildcard(_utilsFnJs); + +var _utilsLogJs = _dereq_('../utils/log.js'); + +var _utilsLogJs2 = _interopRequireDefault(_utilsLogJs); + +var _utilsTimeRangesJs = _dereq_('../utils/time-ranges.js'); + +var _utilsBufferJs = _dereq_('../utils/buffer.js'); + +var _mediaErrorJs = _dereq_('../media-error.js'); + +var _mediaErrorJs2 = _interopRequireDefault(_mediaErrorJs); + +var _globalWindow = _dereq_('global/window'); + +var _globalWindow2 = _interopRequireDefault(_globalWindow); + +var _globalDocument = _dereq_('global/document'); + +var _globalDocument2 = _interopRequireDefault(_globalDocument); + +/** + * Base class for media (HTML5 Video, Flash) controllers + * + * @param {Object=} options Options object + * @param {Function=} ready Ready callback function + * @extends Component + * @class Tech + */ + +var Tech = (function (_Component) { + _inherits(Tech, _Component); + + function Tech() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + var ready = arguments.length <= 1 || arguments[1] === undefined ? function () {} : arguments[1]; + + _classCallCheck(this, Tech); + + // we don't want the tech to report user activity automatically. + // This is done manually in addControlsListeners + options.reportTouchActivity = false; + _Component.call(this, null, options, ready); + + // keep track of whether the current source has played at all to + // implement a very limited played() + this.hasStarted_ = false; + this.on('playing', function () { + this.hasStarted_ = true; + }); + this.on('loadstart', function () { + this.hasStarted_ = false; + }); + + this.textTracks_ = options.textTracks; + this.videoTracks_ = options.videoTracks; + this.audioTracks_ = options.audioTracks; + + // Manually track progress in cases where the browser/flash player doesn't report it. + if (!this.featuresProgressEvents) { + this.manualProgressOn(); + } + + // Manually track timeupdates in cases where the browser/flash player doesn't report it. + if (!this.featuresTimeupdateEvents) { + this.manualTimeUpdatesOn(); + } + + if (options.nativeCaptions === false || options.nativeTextTracks === false) { + this.featuresNativeTextTracks = false; + } + + if (!this.featuresNativeTextTracks) { + this.on('ready', this.emulateTextTracks); + } + + this.initTextTrackListeners(); + this.initTrackListeners(); + + // Turn on component tap events + this.emitTapEvents(); + } + + /** + * List of associated text tracks + * + * @type {TextTrackList} + * @private + */ + + /* Fallbacks for unsupported event types + ================================================================================ */ + // Manually trigger progress events based on changes to the buffered amount + // Many flash players and older HTML5 browsers don't send progress or progress-like events + /** + * Turn on progress events + * + * @method manualProgressOn + */ + + Tech.prototype.manualProgressOn = function manualProgressOn() { + this.on('durationchange', this.onDurationChange); + + this.manualProgress = true; + + // Trigger progress watching when a source begins loading + this.one('ready', this.trackProgress); + }; + + /** + * Turn off progress events + * + * @method manualProgressOff + */ + + Tech.prototype.manualProgressOff = function manualProgressOff() { + this.manualProgress = false; + this.stopTrackingProgress(); + + this.off('durationchange', this.onDurationChange); + }; + + /** + * Track progress + * + * @method trackProgress + */ + + Tech.prototype.trackProgress = function trackProgress() { + this.stopTrackingProgress(); + this.progressInterval = this.setInterval(Fn.bind(this, function () { + // Don't trigger unless buffered amount is greater than last time + + var numBufferedPercent = this.bufferedPercent(); + + if (this.bufferedPercent_ !== numBufferedPercent) { + this.trigger('progress'); + } + + this.bufferedPercent_ = numBufferedPercent; + + if (numBufferedPercent === 1) { + this.stopTrackingProgress(); + } + }), 500); + }; + + /** + * Update duration + * + * @method onDurationChange + */ + + Tech.prototype.onDurationChange = function onDurationChange() { + this.duration_ = this.duration(); + }; + + /** + * Create and get TimeRange object for buffering + * + * @return {TimeRangeObject} + * @method buffered + */ + + Tech.prototype.buffered = function buffered() { + return _utilsTimeRangesJs.createTimeRange(0, 0); + }; + + /** + * Get buffered percent + * + * @return {Number} + * @method bufferedPercent + */ + + Tech.prototype.bufferedPercent = function bufferedPercent() { + return _utilsBufferJs.bufferedPercent(this.buffered(), this.duration_); + }; + + /** + * Stops tracking progress by clearing progress interval + * + * @method stopTrackingProgress + */ + + Tech.prototype.stopTrackingProgress = function stopTrackingProgress() { + this.clearInterval(this.progressInterval); + }; + + /*! Time Tracking -------------------------------------------------------------- */ + /** + * Set event listeners for on play and pause and tracking current time + * + * @method manualTimeUpdatesOn + */ + + Tech.prototype.manualTimeUpdatesOn = function manualTimeUpdatesOn() { + this.manualTimeUpdates = true; + + this.on('play', this.trackCurrentTime); + this.on('pause', this.stopTrackingCurrentTime); + }; + + /** + * Remove event listeners for on play and pause and tracking current time + * + * @method manualTimeUpdatesOff + */ + + Tech.prototype.manualTimeUpdatesOff = function manualTimeUpdatesOff() { + this.manualTimeUpdates = false; + this.stopTrackingCurrentTime(); + this.off('play', this.trackCurrentTime); + this.off('pause', this.stopTrackingCurrentTime); + }; + + /** + * Tracks current time + * + * @method trackCurrentTime + */ + + Tech.prototype.trackCurrentTime = function trackCurrentTime() { + if (this.currentTimeInterval) { + this.stopTrackingCurrentTime(); + } + this.currentTimeInterval = this.setInterval(function () { + this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); + }, 250); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 + }; + + /** + * Turn off play progress tracking (when paused or dragging) + * + * @method stopTrackingCurrentTime + */ + + Tech.prototype.stopTrackingCurrentTime = function stopTrackingCurrentTime() { + this.clearInterval(this.currentTimeInterval); + + // #1002 - if the video ends right before the next timeupdate would happen, + // the progress bar won't make it all the way to the end + this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); + }; + + /** + * Turn off any manual progress or timeupdate tracking + * + * @method dispose + */ + + Tech.prototype.dispose = function dispose() { + + // clear out all tracks because we can't reuse them between techs + this.clearTracks(['audio', 'video', 'text']); + + // Turn off any manual progress or timeupdate tracking + if (this.manualProgress) { + this.manualProgressOff(); + } + + if (this.manualTimeUpdates) { + this.manualTimeUpdatesOff(); + } + + _Component.prototype.dispose.call(this); + }; + + /** + * clear out a track list, or multiple track lists + * + * Note: Techs without source handlers should call this between + * sources for video & audio tracks, as usually you don't want + * to use them between tracks and we have no automatic way to do + * it for you + * + * @method clearTracks + * @param {Array|String} types type(s) of track lists to empty + */ + + Tech.prototype.clearTracks = function clearTracks(types) { + var _this = this; + + types = [].concat(types); + // clear out all tracks because we can't reuse them between techs + types.forEach(function (type) { + var list = _this[type + 'Tracks']() || []; + var i = list.length; + while (i--) { + var track = list[i]; + if (type === 'text') { + _this.removeRemoteTextTrack(track); + } + list.removeTrack_(track); + } + }); + }; + + /** + * Reset the tech. Removes all sources and resets readyState. + * + * @method reset + */ + + Tech.prototype.reset = function reset() {}; + + /** + * When invoked without an argument, returns a MediaError object + * representing the current error state of the player or null if + * there is no error. When invoked with an argument, set the current + * error state of the player. + * @param {MediaError=} err Optional an error object + * @return {MediaError} the current error object or null + * @method error + */ + + Tech.prototype.error = function error(err) { + if (err !== undefined) { + if (err instanceof _mediaErrorJs2['default']) { + this.error_ = err; + } else { + this.error_ = new _mediaErrorJs2['default'](err); + } + this.trigger('error'); + } + return this.error_; + }; + + /** + * Return the time ranges that have been played through for the + * current source. This implementation is incomplete. It does not + * track the played time ranges, only whether the source has played + * at all or not. + * @return {TimeRangeObject} a single time range if this video has + * played or an empty set of ranges if not. + * @method played + */ + + Tech.prototype.played = function played() { + if (this.hasStarted_) { + return _utilsTimeRangesJs.createTimeRange(0, 0); + } + return _utilsTimeRangesJs.createTimeRange(); + }; + + /** + * Set current time + * + * @method setCurrentTime + */ + + Tech.prototype.setCurrentTime = function setCurrentTime() { + // improve the accuracy of manual timeupdates + if (this.manualTimeUpdates) { + this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); + } + }; + + /** + * Initialize texttrack listeners + * + * @method initTextTrackListeners + */ + + Tech.prototype.initTextTrackListeners = function initTextTrackListeners() { + var textTrackListChanges = Fn.bind(this, function () { + this.trigger('texttrackchange'); + }); + + var tracks = this.textTracks(); + + if (!tracks) return; + + tracks.addEventListener('removetrack', textTrackListChanges); + tracks.addEventListener('addtrack', textTrackListChanges); + + this.on('dispose', Fn.bind(this, function () { + tracks.removeEventListener('removetrack', textTrackListChanges); + tracks.removeEventListener('addtrack', textTrackListChanges); + })); + }; + + /** + * Initialize audio and video track listeners + * + * @method initTrackListeners + */ + + Tech.prototype.initTrackListeners = function initTrackListeners() { + var _this2 = this; + + var trackTypes = ['video', 'audio']; + + trackTypes.forEach(function (type) { + var trackListChanges = function trackListChanges() { + _this2.trigger(type + 'trackchange'); + }; + + var tracks = _this2[type + 'Tracks'](); + + tracks.addEventListener('removetrack', trackListChanges); + tracks.addEventListener('addtrack', trackListChanges); + + _this2.on('dispose', function () { + tracks.removeEventListener('removetrack', trackListChanges); + tracks.removeEventListener('addtrack', trackListChanges); + }); + }); + }; + + /** + * Emulate texttracks + * + * @method emulateTextTracks + */ + + Tech.prototype.emulateTextTracks = function emulateTextTracks() { + var _this3 = this; + + var tracks = this.textTracks(); + if (!tracks) { + return; + } + + if (!_globalWindow2['default']['WebVTT'] && this.el().parentNode != null) { + (function () { + var script = _globalDocument2['default'].createElement('script'); + script.src = _this3.options_['vtt.js'] || 'https://cdn.rawgit.com/gkatsev/vtt.js/vjs-v0.12.1/dist/vtt.min.js'; + script.onload = function () { + _this3.trigger('vttjsloaded'); + }; + script.onerror = function () { + _this3.trigger('vttjserror'); + }; + _this3.on('dispose', function () { + script.onload = null; + script.onerror = null; + }); + // but have not loaded yet and we set it to true before the inject so that + // we don't overwrite the injected window.WebVTT if it loads right away + _globalWindow2['default']['WebVTT'] = true; + _this3.el().parentNode.appendChild(script); + })(); + } + + var updateDisplay = function updateDisplay() { + return _this3.trigger('texttrackchange'); + }; + var textTracksChanges = function textTracksChanges() { + updateDisplay(); + + for (var i = 0; i < tracks.length; i++) { + var track = tracks[i]; + track.removeEventListener('cuechange', updateDisplay); + if (track.mode === 'showing') { + track.addEventListener('cuechange', updateDisplay); + } + } + }; + + textTracksChanges(); + tracks.addEventListener('change', textTracksChanges); + + this.on('dispose', function () { + tracks.removeEventListener('change', textTracksChanges); + }); + }; + + /** + * Get videotracks + * + * @returns {VideoTrackList} + * @method videoTracks + */ + + Tech.prototype.videoTracks = function videoTracks() { + this.videoTracks_ = this.videoTracks_ || new _tracksVideoTrackList2['default'](); + return this.videoTracks_; + }; + + /** + * Get audiotracklist + * + * @returns {AudioTrackList} + * @method audioTracks + */ + + Tech.prototype.audioTracks = function audioTracks() { + this.audioTracks_ = this.audioTracks_ || new _tracksAudioTrackList2['default'](); + return this.audioTracks_; + }; + + /* + * Provide default methods for text tracks. + * + * Html5 tech overrides these. + */ + + /** + * Get texttracks + * + * @returns {TextTrackList} + * @method textTracks + */ + + Tech.prototype.textTracks = function textTracks() { + this.textTracks_ = this.textTracks_ || new _tracksTextTrackList2['default'](); + return this.textTracks_; + }; + + /** + * Get remote texttracks + * + * @returns {TextTrackList} + * @method remoteTextTracks + */ + + Tech.prototype.remoteTextTracks = function remoteTextTracks() { + this.remoteTextTracks_ = this.remoteTextTracks_ || new _tracksTextTrackList2['default'](); + return this.remoteTextTracks_; + }; + + /** + * Get remote htmltrackelements + * + * @returns {HTMLTrackElementList} + * @method remoteTextTrackEls + */ + + Tech.prototype.remoteTextTrackEls = function remoteTextTrackEls() { + this.remoteTextTrackEls_ = this.remoteTextTrackEls_ || new _tracksHtmlTrackElementList2['default'](); + return this.remoteTextTrackEls_; + }; + + /** + * Creates and returns a remote text track object + * + * @param {String} kind Text track kind (subtitles, captions, descriptions + * chapters and metadata) + * @param {String=} label Label to identify the text track + * @param {String=} language Two letter language abbreviation + * @return {TextTrackObject} + * @method addTextTrack + */ + + Tech.prototype.addTextTrack = function addTextTrack(kind, label, language) { + if (!kind) { + throw new Error('TextTrack kind is required but was not provided'); + } + + return createTrackHelper(this, kind, label, language); + }; + + /** + * Creates a remote text track object and returns a emulated html track element + * + * @param {Object} options The object should contain values for + * kind, language, label and src (location of the WebVTT file) + * @return {HTMLTrackElement} + * @method addRemoteTextTrack + */ + + Tech.prototype.addRemoteTextTrack = function addRemoteTextTrack(options) { + var track = _utilsMergeOptionsJs2['default'](options, { + tech: this + }); + + var htmlTrackElement = new _tracksHtmlTrackElement2['default'](track); + + // store HTMLTrackElement and TextTrack to remote list + this.remoteTextTrackEls().addTrackElement_(htmlTrackElement); + this.remoteTextTracks().addTrack_(htmlTrackElement.track); + + // must come after remoteTextTracks() + this.textTracks().addTrack_(htmlTrackElement.track); + + return htmlTrackElement; + }; + + /** + * Remove remote texttrack + * + * @param {TextTrackObject} track Texttrack to remove + * @method removeRemoteTextTrack + */ + + Tech.prototype.removeRemoteTextTrack = function removeRemoteTextTrack(track) { + this.textTracks().removeTrack_(track); + + var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); + + // remove HTMLTrackElement and TextTrack from remote list + this.remoteTextTrackEls().removeTrackElement_(trackElement); + this.remoteTextTracks().removeTrack_(track); + }; + + /** + * Provide a default setPoster method for techs + * Poster support for techs should be optional, so we don't want techs to + * break if they don't have a way to set a poster. + * + * @method setPoster + */ + + Tech.prototype.setPoster = function setPoster() {}; + + /* + * Check if the tech can support the given type + * + * The base tech does not support any type, but source handlers might + * overwrite this. + * + * @param {String} type The mimetype to check + * @return {String} 'probably', 'maybe', or '' (empty string) + */ + + Tech.prototype.canPlayType = function canPlayType() { + return ''; + }; + + /* + * Return whether the argument is a Tech or not. + * Can be passed either a Class like `Html5` or a instance like `player.tech_` + * + * @param {Object} component An item to check + * @return {Boolean} Whether it is a tech or not + */ + + Tech.isTech = function isTech(component) { + return component.prototype instanceof Tech || component instanceof Tech || component === Tech; + }; + + /** + * Registers a Tech + * + * @param {String} name Name of the Tech to register + * @param {Object} tech The tech to register + * @static + * @method registerComponent + */ + + Tech.registerTech = function registerTech(name, tech) { + if (!Tech.techs_) { + Tech.techs_ = {}; + } + + if (!Tech.isTech(tech)) { + throw new Error('Tech ' + name + ' must be a Tech'); + } + + Tech.techs_[name] = tech; + return tech; + }; + + /** + * Gets a component by name + * + * @param {String} name Name of the component to get + * @return {Component} + * @static + * @method getComponent + */ + + Tech.getTech = function getTech(name) { + if (Tech.techs_ && Tech.techs_[name]) { + return Tech.techs_[name]; + } + + if (_globalWindow2['default'] && _globalWindow2['default'].videojs && _globalWindow2['default'].videojs[name]) { + _utilsLogJs2['default'].warn('The ' + name + ' tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)'); + return _globalWindow2['default'].videojs[name]; + } + }; + + return Tech; +})(_component2['default']); + +Tech.prototype.textTracks_; + +/** + * List of associated audio tracks + * + * @type {AudioTrackList} + * @private + */ +Tech.prototype.audioTracks_; + +/** + * List of associated video tracks + * + * @type {VideoTrackList} + * @private + */ +Tech.prototype.videoTracks_; + +var createTrackHelper = function createTrackHelper(self, kind, label, language) { + var options = arguments.length <= 4 || arguments[4] === undefined ? {} : arguments[4]; + + var tracks = self.textTracks(); + + options.kind = kind; + + if (label) { + options.label = label; + } + if (language) { + options.language = language; + } + options.tech = self; + + var track = new _tracksTextTrack2['default'](options); + tracks.addTrack_(track); + + return track; +}; + +Tech.prototype.featuresVolumeControl = true; + +// Resizing plugins using request fullscreen reloads the plugin +Tech.prototype.featuresFullscreenResize = false; +Tech.prototype.featuresPlaybackRate = false; + +// Optional events that we can manually mimic with timers +// currently not triggered by video-js-swf +Tech.prototype.featuresProgressEvents = false; +Tech.prototype.featuresTimeupdateEvents = false; + +Tech.prototype.featuresNativeTextTracks = false; + +/* + * A functional mixin for techs that want to use the Source Handler pattern. + * + * ##### EXAMPLE: + * + * Tech.withSourceHandlers.call(MyTech); + * + */ +Tech.withSourceHandlers = function (_Tech) { + /* + * Register a source handler + * Source handlers are scripts for handling specific formats. + * The source handler pattern is used for adaptive formats (HLS, DASH) that + * manually load video data and feed it into a Source Buffer (Media Source Extensions) + * @param {Function} handler The source handler + * @param {Boolean} first Register it before any existing handlers + */ + _Tech.registerSourceHandler = function (handler, index) { + var handlers = _Tech.sourceHandlers; + + if (!handlers) { + handlers = _Tech.sourceHandlers = []; + } + + if (index === undefined) { + // add to the end of the list + index = handlers.length; + } + + handlers.splice(index, 0, handler); + }; + + /* + * Check if the tech can support the given type + * @param {String} type The mimetype to check + * @return {String} 'probably', 'maybe', or '' (empty string) + */ + _Tech.canPlayType = function (type) { + var handlers = _Tech.sourceHandlers || []; + var can = undefined; + + for (var i = 0; i < handlers.length; i++) { + can = handlers[i].canPlayType(type); + + if (can) { + return can; + } + } + + return ''; + }; + + /* + * Return the first source handler that supports the source + * TODO: Answer question: should 'probably' be prioritized over 'maybe' + * @param {Object} source The source object + * @param {Object} options The options passed to the tech + * @returns {Object} The first source handler that supports the source + * @returns {null} Null if no source handler is found + */ + _Tech.selectSourceHandler = function (source, options) { + var handlers = _Tech.sourceHandlers || []; + var can = undefined; + + for (var i = 0; i < handlers.length; i++) { + can = handlers[i].canHandleSource(source, options); + + if (can) { + return handlers[i]; + } + } + + return null; + }; + + /* + * Check if the tech can support the given source + * @param {Object} srcObj The source object + * @param {Object} options The options passed to the tech + * @return {String} 'probably', 'maybe', or '' (empty string) + */ + _Tech.canPlaySource = function (srcObj, options) { + var sh = _Tech.selectSourceHandler(srcObj, options); + + if (sh) { + return sh.canHandleSource(srcObj, options); + } + + return ''; + }; + + /* + * When using a source handler, prefer its implementation of + * any function normally provided by the tech. + */ + var deferrable = ['seekable', 'duration']; + + deferrable.forEach(function (fnName) { + var originalFn = this[fnName]; + + if (typeof originalFn !== 'function') { + return; + } + + this[fnName] = function () { + if (this.sourceHandler_ && this.sourceHandler_[fnName]) { + return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments); + } + return originalFn.apply(this, arguments); + }; + }, _Tech.prototype); + + /* + * Create a function for setting the source using a source object + * and source handlers. + * Should never be called unless a source handler was found. + * @param {Object} source A source object with src and type keys + * @return {Tech} self + */ + _Tech.prototype.setSource = function (source) { + var sh = _Tech.selectSourceHandler(source, this.options_); + + if (!sh) { + // Fall back to a native source hander when unsupported sources are + // deliberately set + if (_Tech.nativeSourceHandler) { + sh = _Tech.nativeSourceHandler; + } else { + _utilsLogJs2['default'].error('No source hander found for the current source.'); + } + } + + // Dispose any existing source handler + this.disposeSourceHandler(); + this.off('dispose', this.disposeSourceHandler); + + // if we have a source and get another one + // then we are loading something new + // than clear all of our current tracks + if (this.currentSource_) { + this.clearTracks(['audio', 'video']); + + this.currentSource_ = null; + } + + if (sh !== _Tech.nativeSourceHandler) { + + this.currentSource_ = source; + + // Catch if someone replaced the src without calling setSource. + // If they do, set currentSource_ to null and dispose our source handler. + this.off(this.el_, 'loadstart', _Tech.prototype.firstLoadStartListener_); + this.off(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_); + this.one(this.el_, 'loadstart', _Tech.prototype.firstLoadStartListener_); + } + + this.sourceHandler_ = sh.handleSource(source, this, this.options_); + this.on('dispose', this.disposeSourceHandler); + + return this; + }; + + // On the first loadstart after setSource + _Tech.prototype.firstLoadStartListener_ = function () { + this.one(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_); + }; + + // On successive loadstarts when setSource has not been called again + _Tech.prototype.successiveLoadStartListener_ = function () { + this.currentSource_ = null; + this.disposeSourceHandler(); + this.one(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_); + }; + + /* + * Clean up any existing source handler + */ + _Tech.prototype.disposeSourceHandler = function () { + if (this.sourceHandler_ && this.sourceHandler_.dispose) { + this.off(this.el_, 'loadstart', _Tech.prototype.firstLoadStartListener_); + this.off(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_); + this.sourceHandler_.dispose(); + this.sourceHandler_ = null; + } + }; +}; + +_component2['default'].registerComponent('Tech', Tech); +// Old name for Tech +_component2['default'].registerComponent('MediaTechController', Tech); +Tech.registerTech('Tech', Tech); +exports['default'] = Tech; +module.exports = exports['default']; + +},{"../component":67,"../media-error.js":108,"../tracks/audio-track":126,"../tracks/audio-track-list":125,"../tracks/html-track-element":128,"../tracks/html-track-element-list":127,"../tracks/text-track":134,"../tracks/text-track-list":132,"../tracks/video-track":139,"../tracks/video-track-list":138,"../utils/buffer.js":141,"../utils/fn.js":144,"../utils/log.js":147,"../utils/merge-options.js":148,"../utils/time-ranges.js":150,"global/document":7,"global/window":8}],125:[function(_dereq_,module,exports){ +/** + * @file audio-track-list.js + */ +'use strict'; + +exports.__esModule = true; + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _trackList = _dereq_('./track-list'); + +var _trackList2 = _interopRequireDefault(_trackList); + +var _utilsBrowserJs = _dereq_('../utils/browser.js'); + +var browser = _interopRequireWildcard(_utilsBrowserJs); + +var _globalDocument = _dereq_('global/document'); + +var _globalDocument2 = _interopRequireDefault(_globalDocument); + +/** + * anywhere we call this function we diverge from the spec + * as we only support one enabled audiotrack at a time + * + * @param {Array|AudioTrackList} list list to work on + * @param {AudioTrack} track the track to skip + */ +var disableOthers = function disableOthers(list, track) { + for (var i = 0; i < list.length; i++) { + if (track.id === list[i].id) { + continue; + } + // another audio track is enabled, disable it + list[i].enabled = false; + } +}; +/** + * A list of possible audio tracks. All functionality is in the + * base class Tracklist and the spec for AudioTrackList is located at: + * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist + * + * interface AudioTrackList : EventTarget { + * readonly attribute unsigned long length; + * getter AudioTrack (unsigned long index); + * AudioTrack? getTrackById(DOMString id); + * + * attribute EventHandler onchange; + * attribute EventHandler onaddtrack; + * attribute EventHandler onremovetrack; + * }; + * + * @param {AudioTrack[]} tracks a list of audio tracks to instantiate the list with + * @extends TrackList + * @class AudioTrackList + */ + +var AudioTrackList = (function (_TrackList) { + _inherits(AudioTrackList, _TrackList); + + function AudioTrackList() { + var tracks = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0]; + + _classCallCheck(this, AudioTrackList); + + var list = undefined; + + // make sure only 1 track is enabled + // sorted from last index to first index + for (var i = tracks.length - 1; i >= 0; i--) { + if (tracks[i].enabled) { + disableOthers(tracks, tracks[i]); + break; + } + } + + // IE8 forces us to implement inheritance ourselves + // as it does not support Object.defineProperty properly + if (browser.IS_IE8) { + list = _globalDocument2['default'].createElement('custom'); + for (var prop in _trackList2['default'].prototype) { + if (prop !== 'constructor') { + list[prop] = _trackList2['default'].prototype[prop]; + } + } + for (var prop in AudioTrackList.prototype) { + if (prop !== 'constructor') { + list[prop] = AudioTrackList.prototype[prop]; + } + } + } + + list = _TrackList.call(this, tracks, list); + list.changing_ = false; + + return list; + } + + AudioTrackList.prototype.addTrack_ = function addTrack_(track) { + var _this = this; + + if (track.enabled) { + disableOthers(this, track); + } + + _TrackList.prototype.addTrack_.call(this, track); + // native tracks don't have this + if (!track.addEventListener) { + return; + } + + track.addEventListener('enabledchange', function () { + // when we are disabling other tracks (since we don't support + // more than one track at a time) we will set changing_ + // to true so that we don't trigger additional change events + if (_this.changing_) { + return; + } + _this.changing_ = true; + disableOthers(_this, track); + _this.changing_ = false; + _this.trigger('change'); + }); + }; + + AudioTrackList.prototype.addTrack = function addTrack(track) { + this.addTrack_(track); + }; + + AudioTrackList.prototype.removeTrack = function removeTrack(track) { + _TrackList.prototype.removeTrack_.call(this, track); + }; + + return AudioTrackList; +})(_trackList2['default']); + +exports['default'] = AudioTrackList; +module.exports = exports['default']; + +},{"../utils/browser.js":140,"./track-list":136,"global/document":7}],126:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _trackEnums = _dereq_('./track-enums'); + +var _track = _dereq_('./track'); + +var _track2 = _interopRequireDefault(_track); + +var _utilsMergeOptions = _dereq_('../utils/merge-options'); + +var _utilsMergeOptions2 = _interopRequireDefault(_utilsMergeOptions); + +var _utilsBrowserJs = _dereq_('../utils/browser.js'); + +var browser = _interopRequireWildcard(_utilsBrowserJs); + +/** + * A single audio text track as defined in: + * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack + * + * interface AudioTrack { + * readonly attribute DOMString id; + * readonly attribute DOMString kind; + * readonly attribute DOMString label; + * readonly attribute DOMString language; + * attribute boolean enabled; + * }; + * + * @param {Object=} options Object of option names and values + * @class AudioTrack + */ + +var AudioTrack = (function (_Track) { + _inherits(AudioTrack, _Track); + + function AudioTrack() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + _classCallCheck(this, AudioTrack); + + var settings = _utilsMergeOptions2['default'](options, { + kind: _trackEnums.AudioTrackKind[options.kind] || '' + }); + // on IE8 this will be a document element + // for every other browser this will be a normal object + var track = _Track.call(this, settings); + var enabled = false; + + if (browser.IS_IE8) { + for (var prop in AudioTrack.prototype) { + if (prop !== 'constructor') { + track[prop] = AudioTrack.prototype[prop]; + } + } + } + + Object.defineProperty(track, 'enabled', { + get: function get() { + return enabled; + }, + set: function set(newEnabled) { + // an invalid or unchanged value + if (typeof newEnabled !== 'boolean' || newEnabled === enabled) { + return; + } + enabled = newEnabled; + this.trigger('enabledchange'); + } + }); + + // if the user sets this track to selected then + // set selected to that true value otherwise + // we keep it false + if (settings.enabled) { + track.enabled = settings.enabled; + } + track.loaded_ = true; + + return track; + } + + return AudioTrack; +})(_track2['default']); + +exports['default'] = AudioTrack; +module.exports = exports['default']; + +},{"../utils/browser.js":140,"../utils/merge-options":148,"./track":137,"./track-enums":135}],127:[function(_dereq_,module,exports){ +/** + * @file html-track-element-list.js + */ + +'use strict'; + +exports.__esModule = true; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +var _utilsBrowserJs = _dereq_('../utils/browser.js'); + +var browser = _interopRequireWildcard(_utilsBrowserJs); + +var _globalDocument = _dereq_('global/document'); + +var _globalDocument2 = _interopRequireDefault(_globalDocument); + +var HtmlTrackElementList = (function () { + function HtmlTrackElementList() { + var trackElements = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0]; + + _classCallCheck(this, HtmlTrackElementList); + + var list = this; + + if (browser.IS_IE8) { + list = _globalDocument2['default'].createElement('custom'); + + for (var prop in HtmlTrackElementList.prototype) { + if (prop !== 'constructor') { + list[prop] = HtmlTrackElementList.prototype[prop]; + } + } + } + + list.trackElements_ = []; + + Object.defineProperty(list, 'length', { + get: function get() { + return this.trackElements_.length; + } + }); + + for (var i = 0, _length = trackElements.length; i < _length; i++) { + list.addTrackElement_(trackElements[i]); + } + + if (browser.IS_IE8) { + return list; + } + } + + HtmlTrackElementList.prototype.addTrackElement_ = function addTrackElement_(trackElement) { + this.trackElements_.push(trackElement); + }; + + HtmlTrackElementList.prototype.getTrackElementByTrack_ = function getTrackElementByTrack_(track) { + var trackElement_ = undefined; + + for (var i = 0, _length2 = this.trackElements_.length; i < _length2; i++) { + if (track === this.trackElements_[i].track) { + trackElement_ = this.trackElements_[i]; + + break; + } + } + + return trackElement_; + }; + + HtmlTrackElementList.prototype.removeTrackElement_ = function removeTrackElement_(trackElement) { + for (var i = 0, _length3 = this.trackElements_.length; i < _length3; i++) { + if (trackElement === this.trackElements_[i]) { + this.trackElements_.splice(i, 1); + + break; + } + } + }; + + return HtmlTrackElementList; +})(); + +exports['default'] = HtmlTrackElementList; +module.exports = exports['default']; + +},{"../utils/browser.js":140,"global/document":7}],128:[function(_dereq_,module,exports){ +/** + * @file html-track-element.js + */ + +'use strict'; + +exports.__esModule = true; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _utilsBrowserJs = _dereq_('../utils/browser.js'); + +var browser = _interopRequireWildcard(_utilsBrowserJs); + +var _globalDocument = _dereq_('global/document'); + +var _globalDocument2 = _interopRequireDefault(_globalDocument); + +var _eventTarget = _dereq_('../event-target'); + +var _eventTarget2 = _interopRequireDefault(_eventTarget); + +var _tracksTextTrack = _dereq_('../tracks/text-track'); + +var _tracksTextTrack2 = _interopRequireDefault(_tracksTextTrack); + +var NONE = 0; +var LOADING = 1; +var LOADED = 2; +var ERROR = 3; + +/** + * https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement + * + * interface HTMLTrackElement : HTMLElement { + * attribute DOMString kind; + * attribute DOMString src; + * attribute DOMString srclang; + * attribute DOMString label; + * attribute boolean default; + * + * const unsigned short NONE = 0; + * const unsigned short LOADING = 1; + * const unsigned short LOADED = 2; + * const unsigned short ERROR = 3; + * readonly attribute unsigned short readyState; + * + * readonly attribute TextTrack track; + * }; + * + * @param {Object} options TextTrack configuration + * @class HTMLTrackElement + */ + +var HTMLTrackElement = (function (_EventTarget) { + _inherits(HTMLTrackElement, _EventTarget); + + function HTMLTrackElement() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + _classCallCheck(this, HTMLTrackElement); + + _EventTarget.call(this); + + var readyState = undefined, + trackElement = this; + + if (browser.IS_IE8) { + trackElement = _globalDocument2['default'].createElement('custom'); + + for (var prop in HTMLTrackElement.prototype) { + if (prop !== 'constructor') { + trackElement[prop] = HTMLTrackElement.prototype[prop]; + } + } + } + + var track = new _tracksTextTrack2['default'](options); + + trackElement.kind = track.kind; + trackElement.src = track.src; + trackElement.srclang = track.language; + trackElement.label = track.label; + trackElement['default'] = track['default']; + + Object.defineProperty(trackElement, 'readyState', { + get: function get() { + return readyState; + } + }); + + Object.defineProperty(trackElement, 'track', { + get: function get() { + return track; + } + }); + + readyState = NONE; + + track.addEventListener('loadeddata', function () { + readyState = LOADED; + + trackElement.trigger({ + type: 'load', + target: trackElement + }); + }); + + if (browser.IS_IE8) { + return trackElement; + } + } + + return HTMLTrackElement; +})(_eventTarget2['default']); + +HTMLTrackElement.prototype.allowedEvents_ = { + load: 'load' +}; + +HTMLTrackElement.NONE = NONE; +HTMLTrackElement.LOADING = LOADING; +HTMLTrackElement.LOADED = LOADED; +HTMLTrackElement.ERROR = ERROR; + +exports['default'] = HTMLTrackElement; +module.exports = exports['default']; + +},{"../event-target":104,"../tracks/text-track":134,"../utils/browser.js":140,"global/document":7}],129:[function(_dereq_,module,exports){ +/** + * @file text-track-cue-list.js + */ +'use strict'; + +exports.__esModule = true; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +var _utilsBrowserJs = _dereq_('../utils/browser.js'); + +var browser = _interopRequireWildcard(_utilsBrowserJs); + +var _globalDocument = _dereq_('global/document'); + +var _globalDocument2 = _interopRequireDefault(_globalDocument); + +/** + * A List of text track cues as defined in: + * https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist + * + * interface TextTrackCueList { + * readonly attribute unsigned long length; + * getter TextTrackCue (unsigned long index); + * TextTrackCue? getCueById(DOMString id); + * }; + * + * @param {Array} cues A list of cues to be initialized with + * @class TextTrackCueList + */ + +var TextTrackCueList = (function () { + function TextTrackCueList(cues) { + _classCallCheck(this, TextTrackCueList); + + var list = this; + + if (browser.IS_IE8) { + list = _globalDocument2['default'].createElement('custom'); + + for (var prop in TextTrackCueList.prototype) { + if (prop !== 'constructor') { + list[prop] = TextTrackCueList.prototype[prop]; + } + } + } + + TextTrackCueList.prototype.setCues_.call(list, cues); + + Object.defineProperty(list, 'length', { + get: function get() { + return this.length_; + } + }); + + if (browser.IS_IE8) { + return list; + } + } + + /** + * A setter for cues in this list + * + * @param {Array} cues an array of cues + * @method setCues_ + * @private + */ + + TextTrackCueList.prototype.setCues_ = function setCues_(cues) { + var oldLength = this.length || 0; + var i = 0; + var l = cues.length; + + this.cues_ = cues; + this.length_ = cues.length; + + var defineProp = function defineProp(index) { + if (!('' + index in this)) { + Object.defineProperty(this, '' + index, { + get: function get() { + return this.cues_[index]; + } + }); + } + }; + + if (oldLength < l) { + i = oldLength; + + for (; i < l; i++) { + defineProp.call(this, i); + } + } + }; + + /** + * Get a cue that is currently in the Cue list by id + * + * @param {String} id + * @method getCueById + * @return {Object} a single cue + */ + + TextTrackCueList.prototype.getCueById = function getCueById(id) { + var result = null; + + for (var i = 0, l = this.length; i < l; i++) { + var cue = this[i]; + + if (cue.id === id) { + result = cue; + break; + } + } + + return result; + }; + + return TextTrackCueList; +})(); + +exports['default'] = TextTrackCueList; +module.exports = exports['default']; + +},{"../utils/browser.js":140,"global/document":7}],130:[function(_dereq_,module,exports){ +/** + * @file text-track-display.js + */ +'use strict'; + +exports.__esModule = true; + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _component = _dereq_('../component'); + +var _component2 = _interopRequireDefault(_component); + +var _menuMenuJs = _dereq_('../menu/menu.js'); + +var _menuMenuJs2 = _interopRequireDefault(_menuMenuJs); + +var _menuMenuItemJs = _dereq_('../menu/menu-item.js'); + +var _menuMenuItemJs2 = _interopRequireDefault(_menuMenuItemJs); + +var _menuMenuButtonJs = _dereq_('../menu/menu-button.js'); + +var _menuMenuButtonJs2 = _interopRequireDefault(_menuMenuButtonJs); + +var _utilsFnJs = _dereq_('../utils/fn.js'); + +var Fn = _interopRequireWildcard(_utilsFnJs); + +var _globalDocument = _dereq_('global/document'); + +var _globalDocument2 = _interopRequireDefault(_globalDocument); + +var _globalWindow = _dereq_('global/window'); + +var _globalWindow2 = _interopRequireDefault(_globalWindow); + +var darkGray = '#222'; +var lightGray = '#ccc'; +var fontMap = { + monospace: 'monospace', + sansSerif: 'sans-serif', + serif: 'serif', + monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace', + monospaceSerif: '"Courier New", monospace', + proportionalSansSerif: 'sans-serif', + proportionalSerif: 'serif', + casual: '"Comic Sans MS", Impact, fantasy', + script: '"Monotype Corsiva", cursive', + smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif' +}; + +/** + * The component for displaying text track cues + * + * @param {Object} player Main Player + * @param {Object=} options Object of option names and values + * @param {Function=} ready Ready callback function + * @extends Component + * @class TextTrackDisplay + */ + +var TextTrackDisplay = (function (_Component) { + _inherits(TextTrackDisplay, _Component); + + function TextTrackDisplay(player, options, ready) { + _classCallCheck(this, TextTrackDisplay); + + _Component.call(this, player, options, ready); + + player.on('loadstart', Fn.bind(this, this.toggleDisplay)); + player.on('texttrackchange', Fn.bind(this, this.updateDisplay)); + + // This used to be called during player init, but was causing an error + // if a track should show by default and the display hadn't loaded yet. + // Should probably be moved to an external track loader when we support + // tracks that don't need a display. + player.ready(Fn.bind(this, function () { + if (player.tech_ && player.tech_['featuresNativeTextTracks']) { + this.hide(); + return; + } + + player.on('fullscreenchange', Fn.bind(this, this.updateDisplay)); + + var tracks = this.options_.playerOptions['tracks'] || []; + for (var i = 0; i < tracks.length; i++) { + var track = tracks[i]; + this.player_.addRemoteTextTrack(track); + } + + var modes = { 'captions': 1, 'subtitles': 1 }; + var trackList = this.player_.textTracks(); + var firstDesc = undefined; + var firstCaptions = undefined; + + if (trackList) { + for (var i = 0; i < trackList.length; i++) { + var track = trackList[i]; + if (track['default']) { + if (track.kind === 'descriptions' && !firstDesc) { + firstDesc = track; + } else if (track.kind in modes && !firstCaptions) { + firstCaptions = track; + } + } + } + + // We want to show the first default track but captions and subtitles + // take precedence over descriptions. + // So, display the first default captions or subtitles track + // and otherwise the first default descriptions track. + if (firstCaptions) { + firstCaptions.mode = 'showing'; + } else if (firstDesc) { + firstDesc.mode = 'showing'; + } + } + })); + } + + /** + * Add cue HTML to display + * + * @param {Number} color Hex number for color, like #f0e + * @param {Number} opacity Value for opacity,0.0 - 1.0 + * @return {RGBAColor} In the form 'rgba(255, 0, 0, 0.3)' + * @method constructColor + */ + + /** + * Toggle display texttracks + * + * @method toggleDisplay + */ + + TextTrackDisplay.prototype.toggleDisplay = function toggleDisplay() { + if (this.player_.tech_ && this.player_.tech_['featuresNativeTextTracks']) { + this.hide(); + } else { + this.show(); + } + }; + + /** + * Create the component's DOM element + * + * @return {Element} + * @method createEl + */ + + TextTrackDisplay.prototype.createEl = function createEl() { + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-text-track-display' + }, { + 'aria-live': 'assertive', + 'aria-atomic': 'true' + }); + }; + + /** + * Clear display texttracks + * + * @method clearDisplay + */ + + TextTrackDisplay.prototype.clearDisplay = function clearDisplay() { + if (typeof _globalWindow2['default']['WebVTT'] === 'function') { + _globalWindow2['default']['WebVTT']['processCues'](_globalWindow2['default'], [], this.el_); + } + }; + + /** + * Update display texttracks + * + * @method updateDisplay + */ + + TextTrackDisplay.prototype.updateDisplay = function updateDisplay() { + var tracks = this.player_.textTracks(); + + this.clearDisplay(); + + if (!tracks) { + return; + } + + // Track display prioritization model: if multiple tracks are 'showing', + // display the first 'subtitles' or 'captions' track which is 'showing', + // otherwise display the first 'descriptions' track which is 'showing' + + var descriptionsTrack = null; + var captionsSubtitlesTrack = null; + + var i = tracks.length; + while (i--) { + var track = tracks[i]; + if (track['mode'] === 'showing') { + if (track['kind'] === 'descriptions') { + descriptionsTrack = track; + } else { + captionsSubtitlesTrack = track; + } + } + } + + if (captionsSubtitlesTrack) { + this.updateForTrack(captionsSubtitlesTrack); + } else if (descriptionsTrack) { + this.updateForTrack(descriptionsTrack); + } + }; + + /** + * Add texttrack to texttrack list + * + * @param {TextTrackObject} track Texttrack object to be added to list + * @method updateForTrack + */ + + TextTrackDisplay.prototype.updateForTrack = function updateForTrack(track) { + if (typeof _globalWindow2['default']['WebVTT'] !== 'function' || !track['activeCues']) { + return; + } + + var overrides = this.player_['textTrackSettings'].getValues(); + + var cues = []; + for (var _i = 0; _i < track['activeCues'].length; _i++) { + cues.push(track['activeCues'][_i]); + } + + _globalWindow2['default']['WebVTT']['processCues'](_globalWindow2['default'], cues, this.el_); + + var i = cues.length; + while (i--) { + var cue = cues[i]; + if (!cue) { + continue; + } + + var cueDiv = cue.displayState; + if (overrides.color) { + cueDiv.firstChild.style.color = overrides.color; + } + if (overrides.textOpacity) { + tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity)); + } + if (overrides.backgroundColor) { + cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor; + } + if (overrides.backgroundOpacity) { + tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity)); + } + if (overrides.windowColor) { + if (overrides.windowOpacity) { + tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity)); + } else { + cueDiv.style.backgroundColor = overrides.windowColor; + } + } + if (overrides.edgeStyle) { + if (overrides.edgeStyle === 'dropshadow') { + cueDiv.firstChild.style.textShadow = '2px 2px 3px ' + darkGray + ', 2px 2px 4px ' + darkGray + ', 2px 2px 5px ' + darkGray; + } else if (overrides.edgeStyle === 'raised') { + cueDiv.firstChild.style.textShadow = '1px 1px ' + darkGray + ', 2px 2px ' + darkGray + ', 3px 3px ' + darkGray; + } else if (overrides.edgeStyle === 'depressed') { + cueDiv.firstChild.style.textShadow = '1px 1px ' + lightGray + ', 0 1px ' + lightGray + ', -1px -1px ' + darkGray + ', 0 -1px ' + darkGray; + } else if (overrides.edgeStyle === 'uniform') { + cueDiv.firstChild.style.textShadow = '0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray; + } + } + if (overrides.fontPercent && overrides.fontPercent !== 1) { + var fontSize = _globalWindow2['default'].parseFloat(cueDiv.style.fontSize); + cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px'; + cueDiv.style.height = 'auto'; + cueDiv.style.top = 'auto'; + cueDiv.style.bottom = '2px'; + } + if (overrides.fontFamily && overrides.fontFamily !== 'default') { + if (overrides.fontFamily === 'small-caps') { + cueDiv.firstChild.style.fontVariant = 'small-caps'; + } else { + cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily]; + } + } + } + }; + + return TextTrackDisplay; +})(_component2['default']); + +function constructColor(color, opacity) { + return 'rgba(' + + // color looks like "#f0e" + parseInt(color[1] + color[1], 16) + ',' + parseInt(color[2] + color[2], 16) + ',' + parseInt(color[3] + color[3], 16) + ',' + opacity + ')'; +} + +/** + * Try to update style + * Some style changes will throw an error, particularly in IE8. Those should be noops. + * + * @param {Element} el The element to be styles + * @param {CSSProperty} style The CSS property to be styled + * @param {CSSStyle} rule The actual style to be applied to the property + * @method tryUpdateStyle + */ +function tryUpdateStyle(el, style, rule) { + // + try { + el.style[style] = rule; + } catch (e) {} +} + +_component2['default'].registerComponent('TextTrackDisplay', TextTrackDisplay); +exports['default'] = TextTrackDisplay; +module.exports = exports['default']; + +},{"../component":67,"../menu/menu-button.js":109,"../menu/menu-item.js":110,"../menu/menu.js":111,"../utils/fn.js":144,"global/document":7,"global/window":8}],131:[function(_dereq_,module,exports){ +/** + * Utilities for capturing text track state and re-creating tracks + * based on a capture. + * + * @file text-track-list-converter.js + */ + +/** + * Examine a single text track and return a JSON-compatible javascript + * object that represents the text track's state. + * @param track {TextTrackObject} the text track to query + * @return {Object} a serializable javascript representation of the + * @private + */ +'use strict'; + +exports.__esModule = true; +var trackToJson_ = function trackToJson_(track) { + var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) { + if (track[prop]) { + acc[prop] = track[prop]; + } + + return acc; + }, { + cues: track.cues && Array.prototype.map.call(track.cues, function (cue) { + return { + startTime: cue.startTime, + endTime: cue.endTime, + text: cue.text, + id: cue.id + }; + }) + }); + + return ret; +}; + +/** + * Examine a tech and return a JSON-compatible javascript array that + * represents the state of all text tracks currently configured. The + * return array is compatible with `jsonToTextTracks`. + * @param tech {tech} the tech object to query + * @return {Array} a serializable javascript representation of the + * @function textTracksToJson + */ +var textTracksToJson = function textTracksToJson(tech) { + + var trackEls = tech.$$('track'); + + var trackObjs = Array.prototype.map.call(trackEls, function (t) { + return t.track; + }); + var tracks = Array.prototype.map.call(trackEls, function (trackEl) { + var json = trackToJson_(trackEl.track); + if (trackEl.src) { + json.src = trackEl.src; + } + return json; + }); + + return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) { + return trackObjs.indexOf(track) === -1; + }).map(trackToJson_)); +}; + +/** + * Creates a set of remote text tracks on a tech based on an array of + * javascript text track representations. + * @param json {Array} an array of text track representation objects, + * like those that would be produced by `textTracksToJson` + * @param tech {tech} the tech to create text tracks on + * @function jsonToTextTracks + */ +var jsonToTextTracks = function jsonToTextTracks(json, tech) { + json.forEach(function (track) { + var addedTrack = tech.addRemoteTextTrack(track).track; + if (!track.src && track.cues) { + track.cues.forEach(function (cue) { + return addedTrack.addCue(cue); + }); + } + }); + + return tech.textTracks(); +}; + +exports['default'] = { textTracksToJson: textTracksToJson, jsonToTextTracks: jsonToTextTracks, trackToJson_: trackToJson_ }; +module.exports = exports['default']; + +},{}],132:[function(_dereq_,module,exports){ +/** + * @file text-track-list.js + */ +'use strict'; + +exports.__esModule = true; + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _trackList = _dereq_('./track-list'); + +var _trackList2 = _interopRequireDefault(_trackList); + +var _utilsFnJs = _dereq_('../utils/fn.js'); + +var Fn = _interopRequireWildcard(_utilsFnJs); + +var _utilsBrowserJs = _dereq_('../utils/browser.js'); + +var browser = _interopRequireWildcard(_utilsBrowserJs); + +var _globalDocument = _dereq_('global/document'); + +var _globalDocument2 = _interopRequireDefault(_globalDocument); + +/** + * A list of possible text tracks. All functionality is in the + * base class TrackList. The spec for TextTrackList is located at: + * @link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist + * + * interface TextTrackList : EventTarget { + * readonly attribute unsigned long length; + * getter TextTrack (unsigned long index); + * TextTrack? getTrackById(DOMString id); + * + * attribute EventHandler onchange; + * attribute EventHandler onaddtrack; + * attribute EventHandler onremovetrack; + * }; + * + * @param {TextTrack[]} tracks A list of tracks to initialize the list with + * @extends TrackList + * @class TextTrackList + */ + +var TextTrackList = (function (_TrackList) { + _inherits(TextTrackList, _TrackList); + + function TextTrackList() { + var tracks = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0]; + + _classCallCheck(this, TextTrackList); + + var list = undefined; + + // IE8 forces us to implement inheritance ourselves + // as it does not support Object.defineProperty properly + if (browser.IS_IE8) { + list = _globalDocument2['default'].createElement('custom'); + for (var prop in _trackList2['default'].prototype) { + if (prop !== 'constructor') { + list[prop] = _trackList2['default'].prototype[prop]; + } + } + for (var prop in TextTrackList.prototype) { + if (prop !== 'constructor') { + list[prop] = TextTrackList.prototype[prop]; + } + } + } + + list = _TrackList.call(this, tracks, list); + return list; + } + + TextTrackList.prototype.addTrack_ = function addTrack_(track) { + _TrackList.prototype.addTrack_.call(this, track); + track.addEventListener('modechange', Fn.bind(this, function () { + this.trigger('change'); + })); + }; + + /** + * Remove TextTrack from TextTrackList + * NOTE: Be mindful of what is passed in as it may be a HTMLTrackElement + * + * @param {TextTrack} rtrack + * @method removeTrack_ + * @private + */ + + TextTrackList.prototype.removeTrack_ = function removeTrack_(rtrack) { + var track = undefined; + + for (var i = 0, l = this.length; i < l; i++) { + if (this[i] === rtrack) { + track = this[i]; + if (track.off) { + track.off(); + } + + this.tracks_.splice(i, 1); + + break; + } + } + + if (!track) { + return; + } + + this.trigger({ + track: track, + type: 'removetrack' + }); + }; + + /** + * Get a TextTrack from TextTrackList by a tracks id + * + * @param {String} id - the id of the track to get + * @method getTrackById + * @return {TextTrack} + * @private + */ + + TextTrackList.prototype.getTrackById = function getTrackById(id) { + var result = null; + + for (var i = 0, l = this.length; i < l; i++) { + var track = this[i]; + + if (track.id === id) { + result = track; + break; + } + } + + return result; + }; + + return TextTrackList; +})(_trackList2['default']); + +exports['default'] = TextTrackList; +module.exports = exports['default']; + +},{"../utils/browser.js":140,"../utils/fn.js":144,"./track-list":136,"global/document":7}],133:[function(_dereq_,module,exports){ +/** + * @file text-track-settings.js + */ +'use strict'; + +exports.__esModule = true; + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _component = _dereq_('../component'); + +var _component2 = _interopRequireDefault(_component); + +var _utilsEventsJs = _dereq_('../utils/events.js'); + +var Events = _interopRequireWildcard(_utilsEventsJs); + +var _utilsFnJs = _dereq_('../utils/fn.js'); + +var Fn = _interopRequireWildcard(_utilsFnJs); + +var _utilsLogJs = _dereq_('../utils/log.js'); + +var _utilsLogJs2 = _interopRequireDefault(_utilsLogJs); + +var _safeJsonParseTuple = _dereq_('safe-json-parse/tuple'); + +var _safeJsonParseTuple2 = _interopRequireDefault(_safeJsonParseTuple); + +var _globalWindow = _dereq_('global/window'); + +var _globalWindow2 = _interopRequireDefault(_globalWindow); + +/** + * Manipulate settings of texttracks + * + * @param {Object} player Main Player + * @param {Object=} options Object of option names and values + * @extends Component + * @class TextTrackSettings + */ + +var TextTrackSettings = (function (_Component) { + _inherits(TextTrackSettings, _Component); + + function TextTrackSettings(player, options) { + _classCallCheck(this, TextTrackSettings); + + _Component.call(this, player, options); + this.hide(); + + // Grab `persistTextTrackSettings` from the player options if not passed in child options + if (options.persistTextTrackSettings === undefined) { + this.options_.persistTextTrackSettings = this.options_.playerOptions.persistTextTrackSettings; + } + + Events.on(this.$('.vjs-done-button'), 'click', Fn.bind(this, function () { + this.saveSettings(); + this.hide(); + })); + + Events.on(this.$('.vjs-default-button'), 'click', Fn.bind(this, function () { + this.$('.vjs-fg-color > select').selectedIndex = 0; + this.$('.vjs-bg-color > select').selectedIndex = 0; + this.$('.window-color > select').selectedIndex = 0; + this.$('.vjs-text-opacity > select').selectedIndex = 0; + this.$('.vjs-bg-opacity > select').selectedIndex = 0; + this.$('.vjs-window-opacity > select').selectedIndex = 0; + this.$('.vjs-edge-style select').selectedIndex = 0; + this.$('.vjs-font-family select').selectedIndex = 0; + this.$('.vjs-font-percent select').selectedIndex = 2; + this.updateDisplay(); + })); + + Events.on(this.$('.vjs-fg-color > select'), 'change', Fn.bind(this, this.updateDisplay)); + Events.on(this.$('.vjs-bg-color > select'), 'change', Fn.bind(this, this.updateDisplay)); + Events.on(this.$('.window-color > select'), 'change', Fn.bind(this, this.updateDisplay)); + Events.on(this.$('.vjs-text-opacity > select'), 'change', Fn.bind(this, this.updateDisplay)); + Events.on(this.$('.vjs-bg-opacity > select'), 'change', Fn.bind(this, this.updateDisplay)); + Events.on(this.$('.vjs-window-opacity > select'), 'change', Fn.bind(this, this.updateDisplay)); + Events.on(this.$('.vjs-font-percent select'), 'change', Fn.bind(this, this.updateDisplay)); + Events.on(this.$('.vjs-edge-style select'), 'change', Fn.bind(this, this.updateDisplay)); + Events.on(this.$('.vjs-font-family select'), 'change', Fn.bind(this, this.updateDisplay)); + + if (this.options_.persistTextTrackSettings) { + this.restoreSettings(); + } + } + + /** + * Create the component's DOM element + * + * @return {Element} + * @method createEl + */ + + TextTrackSettings.prototype.createEl = function createEl() { + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-caption-settings vjs-modal-overlay', + innerHTML: captionOptionsMenuTemplate() + }); + }; + + /** + * Get texttrack settings + * Settings are + * .vjs-edge-style + * .vjs-font-family + * .vjs-fg-color + * .vjs-text-opacity + * .vjs-bg-color + * .vjs-bg-opacity + * .window-color + * .vjs-window-opacity + * + * @return {Object} + * @method getValues + */ + + TextTrackSettings.prototype.getValues = function getValues() { + var textEdge = getSelectedOptionValue(this.$('.vjs-edge-style select')); + var fontFamily = getSelectedOptionValue(this.$('.vjs-font-family select')); + var fgColor = getSelectedOptionValue(this.$('.vjs-fg-color > select')); + var textOpacity = getSelectedOptionValue(this.$('.vjs-text-opacity > select')); + var bgColor = getSelectedOptionValue(this.$('.vjs-bg-color > select')); + var bgOpacity = getSelectedOptionValue(this.$('.vjs-bg-opacity > select')); + var windowColor = getSelectedOptionValue(this.$('.window-color > select')); + var windowOpacity = getSelectedOptionValue(this.$('.vjs-window-opacity > select')); + var fontPercent = _globalWindow2['default']['parseFloat'](getSelectedOptionValue(this.$('.vjs-font-percent > select'))); + + var result = { + 'backgroundOpacity': bgOpacity, + 'textOpacity': textOpacity, + 'windowOpacity': windowOpacity, + 'edgeStyle': textEdge, + 'fontFamily': fontFamily, + 'color': fgColor, + 'backgroundColor': bgColor, + 'windowColor': windowColor, + 'fontPercent': fontPercent + }; + for (var _name in result) { + if (result[_name] === '' || result[_name] === 'none' || _name === 'fontPercent' && result[_name] === 1.00) { + delete result[_name]; + } + } + return result; + }; + + /** + * Set texttrack settings + * Settings are + * .vjs-edge-style + * .vjs-font-family + * .vjs-fg-color + * .vjs-text-opacity + * .vjs-bg-color + * .vjs-bg-opacity + * .window-color + * .vjs-window-opacity + * + * @param {Object} values Object with texttrack setting values + * @method setValues + */ + + TextTrackSettings.prototype.setValues = function setValues(values) { + setSelectedOption(this.$('.vjs-edge-style select'), values.edgeStyle); + setSelectedOption(this.$('.vjs-font-family select'), values.fontFamily); + setSelectedOption(this.$('.vjs-fg-color > select'), values.color); + setSelectedOption(this.$('.vjs-text-opacity > select'), values.textOpacity); + setSelectedOption(this.$('.vjs-bg-color > select'), values.backgroundColor); + setSelectedOption(this.$('.vjs-bg-opacity > select'), values.backgroundOpacity); + setSelectedOption(this.$('.window-color > select'), values.windowColor); + setSelectedOption(this.$('.vjs-window-opacity > select'), values.windowOpacity); + + var fontPercent = values.fontPercent; + + if (fontPercent) { + fontPercent = fontPercent.toFixed(2); + } + + setSelectedOption(this.$('.vjs-font-percent > select'), fontPercent); + }; + + /** + * Restore texttrack settings + * + * @method restoreSettings + */ + + TextTrackSettings.prototype.restoreSettings = function restoreSettings() { + var err = undefined, + values = undefined; + + try { + var _safeParseTuple = _safeJsonParseTuple2['default'](_globalWindow2['default'].localStorage.getItem('vjs-text-track-settings')); + + err = _safeParseTuple[0]; + values = _safeParseTuple[1]; + + if (err) { + _utilsLogJs2['default'].error(err); + } + } catch (e) { + _utilsLogJs2['default'].warn(e); + } + + if (values) { + this.setValues(values); + } + }; + + /** + * Save texttrack settings to local storage + * + * @method saveSettings + */ + + TextTrackSettings.prototype.saveSettings = function saveSettings() { + if (!this.options_.persistTextTrackSettings) { + return; + } + + var values = this.getValues(); + try { + if (Object.getOwnPropertyNames(values).length > 0) { + _globalWindow2['default'].localStorage.setItem('vjs-text-track-settings', JSON.stringify(values)); + } else { + _globalWindow2['default'].localStorage.removeItem('vjs-text-track-settings'); + } + } catch (e) { + _utilsLogJs2['default'].warn(e); + } + }; + + /** + * Update display of texttrack settings + * + * @method updateDisplay + */ + + TextTrackSettings.prototype.updateDisplay = function updateDisplay() { + var ttDisplay = this.player_.getChild('textTrackDisplay'); + if (ttDisplay) { + ttDisplay.updateDisplay(); + } + }; + + return TextTrackSettings; +})(_component2['default']); + +_component2['default'].registerComponent('TextTrackSettings', TextTrackSettings); + +function getSelectedOptionValue(target) { + var selectedOption = undefined; + // not all browsers support selectedOptions, so, fallback to options + if (target.selectedOptions) { + selectedOption = target.selectedOptions[0]; + } else if (target.options) { + selectedOption = target.options[target.options.selectedIndex]; + } + + return selectedOption.value; +} + +function setSelectedOption(target, value) { + if (!value) { + return; + } + + var i = undefined; + for (i = 0; i < target.options.length; i++) { + var option = target.options[i]; + if (option.value === value) { + break; + } + } + + target.selectedIndex = i; +} + +function captionOptionsMenuTemplate() { + var template = '
    \n
    \n
    \n \n \n \n \n \n
    \n
    \n \n \n \n \n \n
    \n
    \n \n \n \n \n \n
    \n
    \n
    \n
    \n \n \n
    \n
    \n \n \n
    \n
    \n \n \n
    \n
    \n
    \n
    \n \n \n
    '; + + return template; +} + +exports['default'] = TextTrackSettings; +module.exports = exports['default']; + +},{"../component":67,"../utils/events.js":143,"../utils/fn.js":144,"../utils/log.js":147,"global/window":8,"safe-json-parse/tuple":58}],134:[function(_dereq_,module,exports){ +/** + * @file text-track.js + */ +'use strict'; + +exports.__esModule = true; + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _textTrackCueList = _dereq_('./text-track-cue-list'); + +var _textTrackCueList2 = _interopRequireDefault(_textTrackCueList); + +var _utilsFnJs = _dereq_('../utils/fn.js'); + +var Fn = _interopRequireWildcard(_utilsFnJs); + +var _trackEnums = _dereq_('./track-enums'); + +var _utilsLogJs = _dereq_('../utils/log.js'); + +var _utilsLogJs2 = _interopRequireDefault(_utilsLogJs); + +var _globalDocument = _dereq_('global/document'); + +var _globalDocument2 = _interopRequireDefault(_globalDocument); + +var _globalWindow = _dereq_('global/window'); + +var _globalWindow2 = _interopRequireDefault(_globalWindow); + +var _trackJs = _dereq_('./track.js'); + +var _trackJs2 = _interopRequireDefault(_trackJs); + +var _utilsUrlJs = _dereq_('../utils/url.js'); + +var _xhr = _dereq_('xhr'); + +var _xhr2 = _interopRequireDefault(_xhr); + +var _utilsMergeOptions = _dereq_('../utils/merge-options'); + +var _utilsMergeOptions2 = _interopRequireDefault(_utilsMergeOptions); + +var _utilsBrowserJs = _dereq_('../utils/browser.js'); + +var browser = _interopRequireWildcard(_utilsBrowserJs); + +/** + * takes a webvtt file contents and parses it into cues + * + * @param {String} srcContent webVTT file contents + * @param {Track} track track to addcues to + */ +var parseCues = function parseCues(srcContent, track) { + var parser = new _globalWindow2['default'].WebVTT.Parser(_globalWindow2['default'], _globalWindow2['default'].vttjs, _globalWindow2['default'].WebVTT.StringDecoder()); + var errors = []; + + parser.oncue = function (cue) { + track.addCue(cue); + }; + + parser.onparsingerror = function (error) { + errors.push(error); + }; + + parser.onflush = function () { + track.trigger({ + type: 'loadeddata', + target: track + }); + }; + + parser.parse(srcContent); + if (errors.length > 0) { + if (console.groupCollapsed) { + console.groupCollapsed('Text Track parsing errors for ' + track.src); + } + errors.forEach(function (error) { + return _utilsLogJs2['default'].error(error); + }); + if (console.groupEnd) { + console.groupEnd(); + } + } + + parser.flush(); +}; + +/** + * load a track from a specifed url + * + * @param {String} src url to load track from + * @param {Track} track track to addcues to + */ +var loadTrack = function loadTrack(src, track) { + var opts = { + uri: src + }; + var crossOrigin = _utilsUrlJs.isCrossOrigin(src); + + if (crossOrigin) { + opts.cors = crossOrigin; + } + + _xhr2['default'](opts, Fn.bind(this, function (err, response, responseBody) { + if (err) { + return _utilsLogJs2['default'].error(err, response); + } + + track.loaded_ = true; + + // Make sure that vttjs has loaded, otherwise, wait till it finished loading + // NOTE: this is only used for the alt/video.novtt.js build + if (typeof _globalWindow2['default'].WebVTT !== 'function') { + if (track.tech_) { + (function () { + var loadHandler = function loadHandler() { + return parseCues(responseBody, track); + }; + track.tech_.on('vttjsloaded', loadHandler); + track.tech_.on('vttjserror', function () { + _utilsLogJs2['default'].error('vttjs failed to load, stopping trying to process ' + track.src); + track.tech_.off('vttjsloaded', loadHandler); + }); + })(); + } + } else { + parseCues(responseBody, track); + } + })); +}; + +/** + * A single text track as defined in: + * @link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack + * + * interface TextTrack : EventTarget { + * readonly attribute TextTrackKind kind; + * readonly attribute DOMString label; + * readonly attribute DOMString language; + * + * readonly attribute DOMString id; + * readonly attribute DOMString inBandMetadataTrackDispatchType; + * + * attribute TextTrackMode mode; + * + * readonly attribute TextTrackCueList? cues; + * readonly attribute TextTrackCueList? activeCues; + * + * void addCue(TextTrackCue cue); + * void removeCue(TextTrackCue cue); + * + * attribute EventHandler oncuechange; + * }; + * + * @param {Object=} options Object of option names and values + * @extends Track + * @class TextTrack + */ + +var TextTrack = (function (_Track) { + _inherits(TextTrack, _Track); + + function TextTrack() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + _classCallCheck(this, TextTrack); + + if (!options.tech) { + throw new Error('A tech was not provided.'); + } + + var settings = _utilsMergeOptions2['default'](options, { + kind: _trackEnums.TextTrackKind[options.kind] || 'subtitles', + language: options.language || options.srclang || '' + }); + var mode = _trackEnums.TextTrackMode[settings.mode] || 'disabled'; + var default_ = settings['default']; + + if (settings.kind === 'metadata' || settings.kind === 'chapters') { + mode = 'hidden'; + } + // on IE8 this will be a document element + // for every other browser this will be a normal object + var tt = _Track.call(this, settings); + tt.tech_ = settings.tech; + + if (browser.IS_IE8) { + for (var prop in TextTrack.prototype) { + if (prop !== 'constructor') { + tt[prop] = TextTrack.prototype[prop]; + } + } + } + + tt.cues_ = []; + tt.activeCues_ = []; + + var cues = new _textTrackCueList2['default'](tt.cues_); + var activeCues = new _textTrackCueList2['default'](tt.activeCues_); + var changed = false; + var timeupdateHandler = Fn.bind(tt, function () { + this.activeCues; + if (changed) { + this.trigger('cuechange'); + changed = false; + } + }); + + if (mode !== 'disabled') { + tt.tech_.on('timeupdate', timeupdateHandler); + } + + Object.defineProperty(tt, 'default', { + get: function get() { + return default_; + }, + set: function set() {} + }); + + Object.defineProperty(tt, 'mode', { + get: function get() { + return mode; + }, + set: function set(newMode) { + if (!_trackEnums.TextTrackMode[newMode]) { + return; + } + mode = newMode; + if (mode === 'showing') { + this.tech_.on('timeupdate', timeupdateHandler); + } + this.trigger('modechange'); + } + }); + + Object.defineProperty(tt, 'cues', { + get: function get() { + if (!this.loaded_) { + return null; + } + + return cues; + }, + set: function set() {} + }); + + Object.defineProperty(tt, 'activeCues', { + get: function get() { + if (!this.loaded_) { + return null; + } + + // nothing to do + if (this.cues.length === 0) { + return activeCues; + } + + var ct = this.tech_.currentTime(); + var active = []; + + for (var i = 0, l = this.cues.length; i < l; i++) { + var cue = this.cues[i]; + + if (cue.startTime <= ct && cue.endTime >= ct) { + active.push(cue); + } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) { + active.push(cue); + } + } + + changed = false; + + if (active.length !== this.activeCues_.length) { + changed = true; + } else { + for (var i = 0; i < active.length; i++) { + if (this.activeCues_.indexOf(active[i]) === -1) { + changed = true; + } + } + } + + this.activeCues_ = active; + activeCues.setCues_(this.activeCues_); + + return activeCues; + }, + set: function set() {} + }); + + if (settings.src) { + tt.src = settings.src; + loadTrack(settings.src, tt); + } else { + tt.loaded_ = true; + } + + return tt; + } + + /** + * cuechange - One or more cues in the track have become active or stopped being active. + */ + + /** + * add a cue to the internal list of cues + * + * @param {Object} cue the cue to add to our internal list + * @method addCue + */ + + TextTrack.prototype.addCue = function addCue(cue) { + var tracks = this.tech_.textTracks(); + + if (tracks) { + for (var i = 0; i < tracks.length; i++) { + if (tracks[i] !== this) { + tracks[i].removeCue(cue); + } + } + } + + this.cues_.push(cue); + this.cues.setCues_(this.cues_); + }; + + /** + * remvoe a cue from our internal list + * + * @param {Object} removeCue the cue to remove from our internal list + * @method removeCue + */ + + TextTrack.prototype.removeCue = function removeCue(_removeCue) { + var removed = false; + + for (var i = 0, l = this.cues_.length; i < l; i++) { + var cue = this.cues_[i]; + + if (cue === _removeCue) { + this.cues_.splice(i, 1); + removed = true; + } + } + + if (removed) { + this.cues.setCues_(this.cues_); + } + }; + + return TextTrack; +})(_trackJs2['default']); + +TextTrack.prototype.allowedEvents_ = { + cuechange: 'cuechange' +}; + +exports['default'] = TextTrack; +module.exports = exports['default']; + +},{"../utils/browser.js":140,"../utils/fn.js":144,"../utils/log.js":147,"../utils/merge-options":148,"../utils/url.js":152,"./text-track-cue-list":129,"./track-enums":135,"./track.js":137,"global/document":7,"global/window":8,"xhr":61}],135:[function(_dereq_,module,exports){ +/** + * @file track-kinds.js + */ + +/** + * https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind + * + * enum VideoTrackKind { + * "alternative", + * "captions", + * "main", + * "sign", + * "subtitles", + * "commentary", + * "", + * }; + */ +'use strict'; + +exports.__esModule = true; +var VideoTrackKind = { + alternative: 'alternative', + captions: 'captions', + main: 'main', + sign: 'sign', + subtitles: 'subtitles', + commentary: 'commentary' +}; + +/** + * https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind + * + * enum AudioTrackKind { + * "alternative", + * "descriptions", + * "main", + * "main-desc", + * "translation", + * "commentary", + * "", + * }; + */ +var AudioTrackKind = { + alternative: 'alternative', + descriptions: 'descriptions', + main: 'main', + 'main-desc': 'main-desc', + translation: 'translation', + commentary: 'commentary' +}; + +/** + * https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackkind + * + * enum TextTrackKind { + * "subtitles", + * "captions", + * "descriptions", + * "chapters", + * "metadata" + * }; + */ +var TextTrackKind = { + subtitles: 'subtitles', + captions: 'captions', + descriptions: 'descriptions', + chapters: 'chapters', + metadata: 'metadata' +}; + +/** + * https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode + * + * enum TextTrackMode { "disabled", "hidden", "showing" }; + */ +var TextTrackMode = { + disabled: 'disabled', + hidden: 'hidden', + showing: 'showing' +}; + +/* jshint ignore:start */ +// we ignore jshint here because it does not see +// AudioTrackKind as defined here +exports['default'] = { VideoTrackKind: VideoTrackKind, AudioTrackKind: AudioTrackKind, TextTrackKind: TextTrackKind, TextTrackMode: TextTrackMode }; + +/* jshint ignore:end */ +module.exports = exports['default']; + +},{}],136:[function(_dereq_,module,exports){ +/** + * @file track-list.js + */ +'use strict'; + +exports.__esModule = true; + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _eventTarget = _dereq_('../event-target'); + +var _eventTarget2 = _interopRequireDefault(_eventTarget); + +var _utilsFnJs = _dereq_('../utils/fn.js'); + +var Fn = _interopRequireWildcard(_utilsFnJs); + +var _utilsBrowserJs = _dereq_('../utils/browser.js'); + +var browser = _interopRequireWildcard(_utilsBrowserJs); + +var _globalDocument = _dereq_('global/document'); + +var _globalDocument2 = _interopRequireDefault(_globalDocument); + +/** + * Common functionaliy between Text, Audio, and Video TrackLists + * Interfaces defined in the following spec: + * @link https://html.spec.whatwg.org/multipage/embedded-content.html + * + * @param {Track[]} tracks A list of tracks to initialize the list with + * @param {Object} list the child object with inheritance done manually for ie8 + * @extends EventTarget + * @class TrackList + */ + +var TrackList = (function (_EventTarget) { + _inherits(TrackList, _EventTarget); + + function TrackList() { + var tracks = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0]; + var list = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1]; + + _classCallCheck(this, TrackList); + + _EventTarget.call(this); + if (!list) { + list = this; + if (browser.IS_IE8) { + list = _globalDocument2['default'].createElement('custom'); + for (var prop in TrackList.prototype) { + if (prop !== 'constructor') { + list[prop] = TrackList.prototype[prop]; + } + } + } + } + + list.tracks_ = []; + Object.defineProperty(list, 'length', { + get: function get() { + return this.tracks_.length; + } + }); + + for (var i = 0; i < tracks.length; i++) { + list.addTrack_(tracks[i]); + } + + return list; + } + + /** + * change - One or more tracks in the track list have been enabled or disabled. + * addtrack - A track has been added to the track list. + * removetrack - A track has been removed from the track list. + */ + + /** + * Add a Track from TrackList + * + * @param {Mixed} track + * @method addTrack_ + * @private + */ + + TrackList.prototype.addTrack_ = function addTrack_(track) { + var index = this.tracks_.length; + + if (!('' + index in this)) { + Object.defineProperty(this, index, { + get: function get() { + return this.tracks_[index]; + } + }); + } + + // Do not add duplicate tracks + if (this.tracks_.indexOf(track) === -1) { + this.tracks_.push(track); + this.trigger({ + track: track, + type: 'addtrack' + }); + } + }; + + /** + * Remove a Track from TrackList + * + * @param {Track} rtrack track to be removed + * @method removeTrack_ + * @private + */ + + TrackList.prototype.removeTrack_ = function removeTrack_(rtrack) { + var track = undefined; + + for (var i = 0, l = this.length; i < l; i++) { + if (this[i] === rtrack) { + track = this[i]; + if (track.off) { + track.off(); + } + + this.tracks_.splice(i, 1); + + break; + } + } + + if (!track) { + return; + } + + this.trigger({ + track: track, + type: 'removetrack' + }); + }; + + /** + * Get a Track from the TrackList by a tracks id + * + * @param {String} id - the id of the track to get + * @method getTrackById + * @return {Track} + * @private + */ + + TrackList.prototype.getTrackById = function getTrackById(id) { + var result = null; + + for (var i = 0, l = this.length; i < l; i++) { + var track = this[i]; + if (track.id === id) { + result = track; + break; + } + } + + return result; + }; + + return TrackList; +})(_eventTarget2['default']); + +TrackList.prototype.allowedEvents_ = { + change: 'change', + addtrack: 'addtrack', + removetrack: 'removetrack' +}; + +// emulate attribute EventHandler support to allow for feature detection +for (var _event in TrackList.prototype.allowedEvents_) { + TrackList.prototype['on' + _event] = null; +} + +exports['default'] = TrackList; +module.exports = exports['default']; + +},{"../event-target":104,"../utils/browser.js":140,"../utils/fn.js":144,"global/document":7}],137:[function(_dereq_,module,exports){ +/** + * @file track.js + */ +'use strict'; + +exports.__esModule = true; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _utilsBrowserJs = _dereq_('../utils/browser.js'); + +var browser = _interopRequireWildcard(_utilsBrowserJs); + +var _globalDocument = _dereq_('global/document'); + +var _globalDocument2 = _interopRequireDefault(_globalDocument); + +var _utilsGuidJs = _dereq_('../utils/guid.js'); + +var Guid = _interopRequireWildcard(_utilsGuidJs); + +var _eventTarget = _dereq_('../event-target'); + +var _eventTarget2 = _interopRequireDefault(_eventTarget); + +/** + * setup the common parts of an audio, video, or text track + * @link https://html.spec.whatwg.org/multipage/embedded-content.html + * + * @param {String} type The type of track we are dealing with audio|video|text + * @param {Object=} options Object of option names and values + * @extends EventTarget + * @class Track + */ + +var Track = (function (_EventTarget) { + _inherits(Track, _EventTarget); + + function Track() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + _classCallCheck(this, Track); + + _EventTarget.call(this); + + var track = this; + if (browser.IS_IE8) { + track = _globalDocument2['default'].createElement('custom'); + for (var prop in Track.prototype) { + if (prop !== 'constructor') { + track[prop] = Track.prototype[prop]; + } + } + } + + var trackProps = { + id: options.id || 'vjs_track_' + Guid.newGUID(), + kind: options.kind || '', + label: options.label || '', + language: options.language || '' + }; + + var _loop = function (key) { + Object.defineProperty(track, key, { + get: function get() { + return trackProps[key]; + }, + set: function set() {} + }); + }; + + for (var key in trackProps) { + _loop(key); + } + + return track; + } + + return Track; +})(_eventTarget2['default']); + +exports['default'] = Track; +module.exports = exports['default']; + +},{"../event-target":104,"../utils/browser.js":140,"../utils/guid.js":146,"global/document":7}],138:[function(_dereq_,module,exports){ +/** + * @file video-track-list.js + */ +'use strict'; + +exports.__esModule = true; + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _trackList = _dereq_('./track-list'); + +var _trackList2 = _interopRequireDefault(_trackList); + +var _utilsBrowserJs = _dereq_('../utils/browser.js'); + +var browser = _interopRequireWildcard(_utilsBrowserJs); + +var _globalDocument = _dereq_('global/document'); + +var _globalDocument2 = _interopRequireDefault(_globalDocument); + +/** + * disable other video tracks before selecting the new one + * + * @param {Array|VideoTrackList} list list to work on + * @param {VideoTrack} track the track to skip + */ +var disableOthers = function disableOthers(list, track) { + for (var i = 0; i < list.length; i++) { + if (track.id === list[i].id) { + continue; + } + // another audio track is enabled, disable it + list[i].selected = false; + } +}; + +/** +* A list of possiblee video tracks. Most functionality is in the + * base class Tracklist and the spec for VideoTrackList is located at: + * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist + * + * interface VideoTrackList : EventTarget { + * readonly attribute unsigned long length; + * getter VideoTrack (unsigned long index); + * VideoTrack? getTrackById(DOMString id); + * readonly attribute long selectedIndex; + * + * attribute EventHandler onchange; + * attribute EventHandler onaddtrack; + * attribute EventHandler onremovetrack; + * }; + * + * @param {VideoTrack[]} tracks a list of video tracks to instantiate the list with + # @extends TrackList + * @class VideoTrackList + */ + +var VideoTrackList = (function (_TrackList) { + _inherits(VideoTrackList, _TrackList); + + function VideoTrackList() { + var tracks = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0]; + + _classCallCheck(this, VideoTrackList); + + var list = undefined; + + // make sure only 1 track is enabled + // sorted from last index to first index + for (var i = tracks.length - 1; i >= 0; i--) { + if (tracks[i].selected) { + disableOthers(tracks, tracks[i]); + break; + } + } + + // IE8 forces us to implement inheritance ourselves + // as it does not support Object.defineProperty properly + if (browser.IS_IE8) { + list = _globalDocument2['default'].createElement('custom'); + for (var prop in _trackList2['default'].prototype) { + if (prop !== 'constructor') { + list[prop] = _trackList2['default'].prototype[prop]; + } + } + for (var prop in VideoTrackList.prototype) { + if (prop !== 'constructor') { + list[prop] = VideoTrackList.prototype[prop]; + } + } + } + + list = _TrackList.call(this, tracks, list); + list.changing_ = false; + + Object.defineProperty(list, 'selectedIndex', { + get: function get() { + for (var i = 0; i < this.length; i++) { + if (this[i].selected) { + return i; + } + } + return -1; + }, + set: function set() {} + }); + + return list; + } + + VideoTrackList.prototype.addTrack_ = function addTrack_(track) { + var _this = this; + + if (track.selected) { + disableOthers(this, track); + } + + _TrackList.prototype.addTrack_.call(this, track); + // native tracks don't have this + if (!track.addEventListener) { + return; + } + track.addEventListener('selectedchange', function () { + if (_this.changing_) { + return; + } + _this.changing_ = true; + disableOthers(_this, track); + _this.changing_ = false; + _this.trigger('change'); + }); + }; + + VideoTrackList.prototype.addTrack = function addTrack(track) { + this.addTrack_(track); + }; + + VideoTrackList.prototype.removeTrack = function removeTrack(track) { + _TrackList.prototype.removeTrack_.call(this, track); + }; + + return VideoTrackList; +})(_trackList2['default']); + +exports['default'] = VideoTrackList; +module.exports = exports['default']; + +},{"../utils/browser.js":140,"./track-list":136,"global/document":7}],139:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _trackEnums = _dereq_('./track-enums'); + +var _track = _dereq_('./track'); + +var _track2 = _interopRequireDefault(_track); + +var _utilsMergeOptions = _dereq_('../utils/merge-options'); + +var _utilsMergeOptions2 = _interopRequireDefault(_utilsMergeOptions); + +var _utilsBrowserJs = _dereq_('../utils/browser.js'); + +var browser = _interopRequireWildcard(_utilsBrowserJs); + +/** + * A single video text track as defined in: + * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack + * + * interface VideoTrack { + * readonly attribute DOMString id; + * readonly attribute DOMString kind; + * readonly attribute DOMString label; + * readonly attribute DOMString language; + * attribute boolean selected; + * }; + * + * @param {Object=} options Object of option names and values + * @class VideoTrack + */ + +var VideoTrack = (function (_Track) { + _inherits(VideoTrack, _Track); + + function VideoTrack() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + _classCallCheck(this, VideoTrack); + + var settings = _utilsMergeOptions2['default'](options, { + kind: _trackEnums.VideoTrackKind[options.kind] || '' + }); + + // on IE8 this will be a document element + // for every other browser this will be a normal object + var track = _Track.call(this, settings); + var selected = false; + + if (browser.IS_IE8) { + for (var prop in VideoTrack.prototype) { + if (prop !== 'constructor') { + track[prop] = VideoTrack.prototype[prop]; + } + } + } + + Object.defineProperty(track, 'selected', { + get: function get() { + return selected; + }, + set: function set(newSelected) { + // an invalid or unchanged value + if (typeof newSelected !== 'boolean' || newSelected === selected) { + return; + } + selected = newSelected; + this.trigger('selectedchange'); + } + }); + + // if the user sets this track to selected then + // set selected to that true value otherwise + // we keep it false + if (settings.selected) { + track.selected = settings.selected; + } + + return track; + } + + return VideoTrack; +})(_track2['default']); + +exports['default'] = VideoTrack; +module.exports = exports['default']; + +},{"../utils/browser.js":140,"../utils/merge-options":148,"./track":137,"./track-enums":135}],140:[function(_dereq_,module,exports){ +/** + * @file browser.js + */ +'use strict'; + +exports.__esModule = true; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _globalDocument = _dereq_('global/document'); + +var _globalDocument2 = _interopRequireDefault(_globalDocument); + +var _globalWindow = _dereq_('global/window'); + +var _globalWindow2 = _interopRequireDefault(_globalWindow); + +var USER_AGENT = _globalWindow2['default'].navigator.userAgent; +var webkitVersionMap = /AppleWebKit\/([\d.]+)/i.exec(USER_AGENT); +var appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null; + +/* + * Device is an iPhone + * + * @type {Boolean} + * @constant + * @private + */ +var IS_IPAD = /iPad/i.test(USER_AGENT); + +exports.IS_IPAD = IS_IPAD; +// The Facebook app's UIWebView identifies as both an iPhone and iPad, so +// to identify iPhones, we need to exclude iPads. +// http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/ +var IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD; +exports.IS_IPHONE = IS_IPHONE; +var IS_IPOD = /iPod/i.test(USER_AGENT); +exports.IS_IPOD = IS_IPOD; +var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD; + +exports.IS_IOS = IS_IOS; +var IOS_VERSION = (function () { + var match = USER_AGENT.match(/OS (\d+)_/i); + if (match && match[1]) { + return match[1]; + } +})(); + +exports.IOS_VERSION = IOS_VERSION; +var IS_ANDROID = /Android/i.test(USER_AGENT); +exports.IS_ANDROID = IS_ANDROID; +var ANDROID_VERSION = (function () { + // This matches Android Major.Minor.Patch versions + // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned + var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i), + major, + minor; + + if (!match) { + return null; + } + + major = match[1] && parseFloat(match[1]); + minor = match[2] && parseFloat(match[2]); + + if (major && minor) { + return parseFloat(match[1] + '.' + match[2]); + } else if (major) { + return major; + } else { + return null; + } +})(); +exports.ANDROID_VERSION = ANDROID_VERSION; +// Old Android is defined as Version older than 2.3, and requiring a webkit version of the android browser +var IS_OLD_ANDROID = IS_ANDROID && /webkit/i.test(USER_AGENT) && ANDROID_VERSION < 2.3; +exports.IS_OLD_ANDROID = IS_OLD_ANDROID; +var IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537; + +exports.IS_NATIVE_ANDROID = IS_NATIVE_ANDROID; +var IS_FIREFOX = /Firefox/i.test(USER_AGENT); +exports.IS_FIREFOX = IS_FIREFOX; +var IS_EDGE = /Edge/i.test(USER_AGENT); +exports.IS_EDGE = IS_EDGE; +var IS_CHROME = !IS_EDGE && /Chrome/i.test(USER_AGENT); +exports.IS_CHROME = IS_CHROME; +var IS_IE8 = /MSIE\s8\.0/.test(USER_AGENT); + +exports.IS_IE8 = IS_IE8; +var TOUCH_ENABLED = !!('ontouchstart' in _globalWindow2['default'] || _globalWindow2['default'].DocumentTouch && _globalDocument2['default'] instanceof _globalWindow2['default'].DocumentTouch); +exports.TOUCH_ENABLED = TOUCH_ENABLED; +var BACKGROUND_SIZE_SUPPORTED = ('backgroundSize' in _globalDocument2['default'].createElement('video').style); +exports.BACKGROUND_SIZE_SUPPORTED = BACKGROUND_SIZE_SUPPORTED; + +},{"global/document":7,"global/window":8}],141:[function(_dereq_,module,exports){ +/** + * @file buffer.js + */ +'use strict'; + +exports.__esModule = true; +exports.bufferedPercent = bufferedPercent; + +var _timeRangesJs = _dereq_('./time-ranges.js'); + +/** + * Compute how much your video has been buffered + * + * @param {Object} Buffered object + * @param {Number} Total duration + * @return {Number} Percent buffered of the total duration + * @private + * @function bufferedPercent + */ + +function bufferedPercent(buffered, duration) { + var bufferedDuration = 0, + start, + end; + + if (!duration) { + return 0; + } + + if (!buffered || !buffered.length) { + buffered = _timeRangesJs.createTimeRange(0, 0); + } + + for (var i = 0; i < buffered.length; i++) { + start = buffered.start(i); + end = buffered.end(i); + + // buffered end can be bigger than duration by a very small fraction + if (end > duration) { + end = duration; + } + + bufferedDuration += end - start; + } + + return bufferedDuration / duration; +} + +},{"./time-ranges.js":150}],142:[function(_dereq_,module,exports){ +/** + * @file dom.js + */ +'use strict'; + +exports.__esModule = true; +exports.getEl = getEl; +exports.createEl = createEl; +exports.textContent = textContent; +exports.insertElFirst = insertElFirst; +exports.getElData = getElData; +exports.hasElData = hasElData; +exports.removeElData = removeElData; +exports.hasElClass = hasElClass; +exports.addElClass = addElClass; +exports.removeElClass = removeElClass; +exports.toggleElClass = toggleElClass; +exports.setElAttributes = setElAttributes; +exports.getElAttributes = getElAttributes; +exports.blockTextSelection = blockTextSelection; +exports.unblockTextSelection = unblockTextSelection; +exports.findElPosition = findElPosition; +exports.getPointerPosition = getPointerPosition; +exports.isEl = isEl; +exports.isTextNode = isTextNode; +exports.emptyEl = emptyEl; +exports.normalizeContent = normalizeContent; +exports.appendContent = appendContent; +exports.insertContent = insertContent; + +var _templateObject = _taggedTemplateLiteralLoose(['Setting attributes in the second argument of createEl()\n has been deprecated. Use the third argument instead.\n createEl(type, properties, attributes). Attempting to set ', ' to ', '.'], ['Setting attributes in the second argument of createEl()\n has been deprecated. Use the third argument instead.\n createEl(type, properties, attributes). Attempting to set ', ' to ', '.']); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _taggedTemplateLiteralLoose(strings, raw) { strings.raw = raw; return strings; } + +var _globalDocument = _dereq_('global/document'); + +var _globalDocument2 = _interopRequireDefault(_globalDocument); + +var _globalWindow = _dereq_('global/window'); + +var _globalWindow2 = _interopRequireDefault(_globalWindow); + +var _guidJs = _dereq_('./guid.js'); + +var Guid = _interopRequireWildcard(_guidJs); + +var _logJs = _dereq_('./log.js'); + +var _logJs2 = _interopRequireDefault(_logJs); + +var _tsml = _dereq_('tsml'); + +var _tsml2 = _interopRequireDefault(_tsml); + +/** + * Detect if a value is a string with any non-whitespace characters. + * + * @param {String} str + * @return {Boolean} + */ +function isNonBlankString(str) { + return typeof str === 'string' && /\S/.test(str); +} + +/** + * Throws an error if the passed string has whitespace. This is used by + * class methods to be relatively consistent with the classList API. + * + * @param {String} str + * @return {Boolean} + */ +function throwIfWhitespace(str) { + if (/\s/.test(str)) { + throw new Error('class has illegal whitespace characters'); + } +} + +/** + * Produce a regular expression for matching a class name. + * + * @param {String} className + * @return {RegExp} + */ +function classRegExp(className) { + return new RegExp('(^|\\s)' + className + '($|\\s)'); +} + +/** + * Creates functions to query the DOM using a given method. + * + * @function createQuerier + * @private + * @param {String} method + * @return {Function} + */ +function createQuerier(method) { + return function (selector, context) { + if (!isNonBlankString(selector)) { + return _globalDocument2['default'][method](null); + } + if (isNonBlankString(context)) { + context = _globalDocument2['default'].querySelector(context); + } + return (isEl(context) ? context : _globalDocument2['default'])[method](selector); + }; +} + +/** + * Shorthand for document.getElementById() + * Also allows for CSS (jQuery) ID syntax. But nothing other than IDs. + * + * @param {String} id Element ID + * @return {Element} Element with supplied ID + * @function getEl + */ + +function getEl(id) { + if (id.indexOf('#') === 0) { + id = id.slice(1); + } + + return _globalDocument2['default'].getElementById(id); +} + +/** + * Creates an element and applies properties. + * + * @param {String} [tagName='div'] Name of tag to be created. + * @param {Object} [properties={}] Element properties to be applied. + * @param {Object} [attributes={}] Element attributes to be applied. + * @return {Element} + * @function createEl + */ + +function createEl() { + var tagName = arguments.length <= 0 || arguments[0] === undefined ? 'div' : arguments[0]; + var properties = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + var attributes = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; + + var el = _globalDocument2['default'].createElement(tagName); + + Object.getOwnPropertyNames(properties).forEach(function (propName) { + var val = properties[propName]; + + // See #2176 + // We originally were accepting both properties and attributes in the + // same object, but that doesn't work so well. + if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') { + _logJs2['default'].warn(_tsml2['default'](_templateObject, propName, val)); + el.setAttribute(propName, val); + } else { + el[propName] = val; + } + }); + + Object.getOwnPropertyNames(attributes).forEach(function (attrName) { + var val = attributes[attrName]; + el.setAttribute(attrName, attributes[attrName]); + }); + + return el; +} + +/** + * Injects text into an element, replacing any existing contents entirely. + * + * @param {Element} el + * @param {String} text + * @return {Element} + * @function textContent + */ + +function textContent(el, text) { + if (typeof el.textContent === 'undefined') { + el.innerText = text; + } else { + el.textContent = text; + } +} + +/** + * Insert an element as the first child node of another + * + * @param {Element} child Element to insert + * @param {Element} parent Element to insert child into + * @private + * @function insertElFirst + */ + +function insertElFirst(child, parent) { + if (parent.firstChild) { + parent.insertBefore(child, parent.firstChild); + } else { + parent.appendChild(child); + } +} + +/** + * Element Data Store. Allows for binding data to an element without putting it directly on the element. + * Ex. Event listeners are stored here. + * (also from jsninja.com, slightly modified and updated for closure compiler) + * + * @type {Object} + * @private + */ +var elData = {}; + +/* + * Unique attribute name to store an element's guid in + * + * @type {String} + * @constant + * @private + */ +var elIdAttr = 'vdata' + new Date().getTime(); + +/** + * Returns the cache object where data for an element is stored + * + * @param {Element} el Element to store data for. + * @return {Object} + * @function getElData + */ + +function getElData(el) { + var id = el[elIdAttr]; + + if (!id) { + id = el[elIdAttr] = Guid.newGUID(); + } + + if (!elData[id]) { + elData[id] = {}; + } + + return elData[id]; +} + +/** + * Returns whether or not an element has cached data + * + * @param {Element} el A dom element + * @return {Boolean} + * @private + * @function hasElData + */ + +function hasElData(el) { + var id = el[elIdAttr]; + + if (!id) { + return false; + } + + return !!Object.getOwnPropertyNames(elData[id]).length; +} + +/** + * Delete data for the element from the cache and the guid attr from getElementById + * + * @param {Element} el Remove data for an element + * @private + * @function removeElData + */ + +function removeElData(el) { + var id = el[elIdAttr]; + + if (!id) { + return; + } + + // Remove all stored data + delete elData[id]; + + // Remove the elIdAttr property from the DOM node + try { + delete el[elIdAttr]; + } catch (e) { + if (el.removeAttribute) { + el.removeAttribute(elIdAttr); + } else { + // IE doesn't appear to support removeAttribute on the document element + el[elIdAttr] = null; + } + } +} + +/** + * Check if an element has a CSS class + * + * @function hasElClass + * @param {Element} element Element to check + * @param {String} classToCheck Classname to check + */ + +function hasElClass(element, classToCheck) { + if (element.classList) { + return element.classList.contains(classToCheck); + } else { + throwIfWhitespace(classToCheck); + return classRegExp(classToCheck).test(element.className); + } +} + +/** + * Add a CSS class name to an element + * + * @function addElClass + * @param {Element} element Element to add class name to + * @param {String} classToAdd Classname to add + */ + +function addElClass(element, classToAdd) { + if (element.classList) { + element.classList.add(classToAdd); + + // Don't need to `throwIfWhitespace` here because `hasElClass` will do it + // in the case of classList not being supported. + } else if (!hasElClass(element, classToAdd)) { + element.className = (element.className + ' ' + classToAdd).trim(); + } + + return element; +} + +/** + * Remove a CSS class name from an element + * + * @function removeElClass + * @param {Element} element Element to remove from class name + * @param {String} classToRemove Classname to remove + */ + +function removeElClass(element, classToRemove) { + if (element.classList) { + element.classList.remove(classToRemove); + } else { + throwIfWhitespace(classToRemove); + element.className = element.className.split(/\s+/).filter(function (c) { + return c !== classToRemove; + }).join(' '); + } + + return element; +} + +/** + * Adds or removes a CSS class name on an element depending on an optional + * condition or the presence/absence of the class name. + * + * @function toggleElClass + * @param {Element} element + * @param {String} classToToggle + * @param {Boolean|Function} [predicate] + * Can be a function that returns a Boolean. If `true`, the class + * will be added; if `false`, the class will be removed. If not + * given, the class will be added if not present and vice versa. + */ + +function toggleElClass(element, classToToggle, predicate) { + + // This CANNOT use `classList` internally because IE does not support the + // second parameter to the `classList.toggle()` method! Which is fine because + // `classList` will be used by the add/remove functions. + var has = hasElClass(element, classToToggle); + + if (typeof predicate === 'function') { + predicate = predicate(element, classToToggle); + } + + if (typeof predicate !== 'boolean') { + predicate = !has; + } + + // If the necessary class operation matches the current state of the + // element, no action is required. + if (predicate === has) { + return; + } + + if (predicate) { + addElClass(element, classToToggle); + } else { + removeElClass(element, classToToggle); + } + + return element; +} + +/** + * Apply attributes to an HTML element. + * + * @param {Element} el Target element. + * @param {Object=} attributes Element attributes to be applied. + * @private + * @function setElAttributes + */ + +function setElAttributes(el, attributes) { + Object.getOwnPropertyNames(attributes).forEach(function (attrName) { + var attrValue = attributes[attrName]; + + if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) { + el.removeAttribute(attrName); + } else { + el.setAttribute(attrName, attrValue === true ? '' : attrValue); + } + }); +} + +/** + * Get an element's attribute values, as defined on the HTML tag + * Attributes are not the same as properties. They're defined on the tag + * or with setAttribute (which shouldn't be used with HTML) + * This will return true or false for boolean attributes. + * + * @param {Element} tag Element from which to get tag attributes + * @return {Object} + * @private + * @function getElAttributes + */ + +function getElAttributes(tag) { + var obj, knownBooleans, attrs, attrName, attrVal; + + obj = {}; + + // known boolean attributes + // we can check for matching boolean properties, but older browsers + // won't know about HTML5 boolean attributes that we still read from + knownBooleans = ',' + 'autoplay,controls,loop,muted,default' + ','; + + if (tag && tag.attributes && tag.attributes.length > 0) { + attrs = tag.attributes; + + for (var i = attrs.length - 1; i >= 0; i--) { + attrName = attrs[i].name; + attrVal = attrs[i].value; + + // check for known booleans + // the matching element property will return a value for typeof + if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) { + // the value of an included boolean attribute is typically an empty + // string ('') which would equal false if we just check for a false value. + // we also don't want support bad code like autoplay='false' + attrVal = attrVal !== null ? true : false; + } + + obj[attrName] = attrVal; + } + } + + return obj; +} + +/** + * Attempt to block the ability to select text while dragging controls + * + * @return {Boolean} + * @function blockTextSelection + */ + +function blockTextSelection() { + _globalDocument2['default'].body.focus(); + _globalDocument2['default'].onselectstart = function () { + return false; + }; +} + +/** + * Turn off text selection blocking + * + * @return {Boolean} + * @function unblockTextSelection + */ + +function unblockTextSelection() { + _globalDocument2['default'].onselectstart = function () { + return true; + }; +} + +/** + * Offset Left + * getBoundingClientRect technique from + * John Resig http://ejohn.org/blog/getboundingclientrect-is-awesome/ + * + * @function findElPosition + * @param {Element} el Element from which to get offset + * @return {Object} + */ + +function findElPosition(el) { + var box = undefined; + + if (el.getBoundingClientRect && el.parentNode) { + box = el.getBoundingClientRect(); + } + + if (!box) { + return { + left: 0, + top: 0 + }; + } + + var docEl = _globalDocument2['default'].documentElement; + var body = _globalDocument2['default'].body; + + var clientLeft = docEl.clientLeft || body.clientLeft || 0; + var scrollLeft = _globalWindow2['default'].pageXOffset || body.scrollLeft; + var left = box.left + scrollLeft - clientLeft; + + var clientTop = docEl.clientTop || body.clientTop || 0; + var scrollTop = _globalWindow2['default'].pageYOffset || body.scrollTop; + var top = box.top + scrollTop - clientTop; + + // Android sometimes returns slightly off decimal values, so need to round + return { + left: Math.round(left), + top: Math.round(top) + }; +} + +/** + * Get pointer position in element + * Returns an object with x and y coordinates. + * The base on the coordinates are the bottom left of the element. + * + * @function getPointerPosition + * @param {Element} el Element on which to get the pointer position on + * @param {Event} event Event object + * @return {Object} This object will have x and y coordinates corresponding to the mouse position + */ + +function getPointerPosition(el, event) { + var position = {}; + var box = findElPosition(el); + var boxW = el.offsetWidth; + var boxH = el.offsetHeight; + + var boxY = box.top; + var boxX = box.left; + var pageY = event.pageY; + var pageX = event.pageX; + + if (event.changedTouches) { + pageX = event.changedTouches[0].pageX; + pageY = event.changedTouches[0].pageY; + } + + position.y = Math.max(0, Math.min(1, (boxY - pageY + boxH) / boxH)); + position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW)); + + return position; +} + +/** + * Determines, via duck typing, whether or not a value is a DOM element. + * + * @function isEl + * @param {Mixed} value + * @return {Boolean} + */ + +function isEl(value) { + return !!value && typeof value === 'object' && value.nodeType === 1; +} + +/** + * Determines, via duck typing, whether or not a value is a text node. + * + * @param {Mixed} value + * @return {Boolean} + */ + +function isTextNode(value) { + return !!value && typeof value === 'object' && value.nodeType === 3; +} + +/** + * Empties the contents of an element. + * + * @function emptyEl + * @param {Element} el + * @return {Element} + */ + +function emptyEl(el) { + while (el.firstChild) { + el.removeChild(el.firstChild); + } + return el; +} + +/** + * Normalizes content for eventual insertion into the DOM. + * + * This allows a wide range of content definition methods, but protects + * from falling into the trap of simply writing to `innerHTML`, which is + * an XSS concern. + * + * The content for an element can be passed in multiple types and + * combinations, whose behavior is as follows: + * + * - String + * Normalized into a text node. + * + * - Element, TextNode + * Passed through. + * + * - Array + * A one-dimensional array of strings, elements, nodes, or functions (which + * return single strings, elements, or nodes). + * + * - Function + * If the sole argument, is expected to produce a string, element, + * node, or array. + * + * @function normalizeContent + * @param {String|Element|TextNode|Array|Function} content + * @return {Array} + */ + +function normalizeContent(content) { + + // First, invoke content if it is a function. If it produces an array, + // that needs to happen before normalization. + if (typeof content === 'function') { + content = content(); + } + + // Next up, normalize to an array, so one or many items can be normalized, + // filtered, and returned. + return (Array.isArray(content) ? content : [content]).map(function (value) { + + // First, invoke value if it is a function to produce a new value, + // which will be subsequently normalized to a Node of some kind. + if (typeof value === 'function') { + value = value(); + } + + if (isEl(value) || isTextNode(value)) { + return value; + } + + if (typeof value === 'string' && /\S/.test(value)) { + return _globalDocument2['default'].createTextNode(value); + } + }).filter(function (value) { + return value; + }); +} + +/** + * Normalizes and appends content to an element. + * + * @function appendContent + * @param {Element} el + * @param {String|Element|TextNode|Array|Function} content + * See: `normalizeContent` + * @return {Element} + */ + +function appendContent(el, content) { + normalizeContent(content).forEach(function (node) { + return el.appendChild(node); + }); + return el; +} + +/** + * Normalizes and inserts content into an element; this is identical to + * `appendContent()`, except it empties the element first. + * + * @function insertContent + * @param {Element} el + * @param {String|Element|TextNode|Array|Function} content + * See: `normalizeContent` + * @return {Element} + */ + +function insertContent(el, content) { + return appendContent(emptyEl(el), content); +} + +/** + * Finds a single DOM element matching `selector` within the optional + * `context` of another DOM element (defaulting to `document`). + * + * @function $ + * @param {String} selector + * A valid CSS selector, which will be passed to `querySelector`. + * + * @param {Element|String} [context=document] + * A DOM element within which to query. Can also be a selector + * string in which case the first matching element will be used + * as context. If missing (or no element matches selector), falls + * back to `document`. + * + * @return {Element|null} + */ +var $ = createQuerier('querySelector'); + +exports.$ = $; +/** + * Finds a all DOM elements matching `selector` within the optional + * `context` of another DOM element (defaulting to `document`). + * + * @function $$ + * @param {String} selector + * A valid CSS selector, which will be passed to `querySelectorAll`. + * + * @param {Element|String} [context=document] + * A DOM element within which to query. Can also be a selector + * string in which case the first matching element will be used + * as context. If missing (or no element matches selector), falls + * back to `document`. + * + * @return {NodeList} + */ +var $$ = createQuerier('querySelectorAll'); +exports.$$ = $$; + +},{"./guid.js":146,"./log.js":147,"global/document":7,"global/window":8,"tsml":60}],143:[function(_dereq_,module,exports){ +/** + * @file events.js + * + * Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/) + * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible) + * This should work very similarly to jQuery's events, however it's based off the book version which isn't as + * robust as jquery's, so there's probably some differences. + */ + +'use strict'; + +exports.__esModule = true; +exports.on = on; +exports.off = off; +exports.trigger = trigger; +exports.one = one; +exports.fixEvent = fixEvent; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +var _domJs = _dereq_('./dom.js'); + +var Dom = _interopRequireWildcard(_domJs); + +var _guidJs = _dereq_('./guid.js'); + +var Guid = _interopRequireWildcard(_guidJs); + +var _globalWindow = _dereq_('global/window'); + +var _globalWindow2 = _interopRequireDefault(_globalWindow); + +var _globalDocument = _dereq_('global/document'); + +var _globalDocument2 = _interopRequireDefault(_globalDocument); + +/** + * Add an event listener to element + * It stores the handler function in a separate cache object + * and adds a generic handler to the element's event, + * along with a unique id (guid) to the element. + * + * @param {Element|Object} elem Element or object to bind listeners to + * @param {String|Array} type Type of event to bind to. + * @param {Function} fn Event listener. + * @method on + */ + +function on(elem, type, fn) { + if (Array.isArray(type)) { + return _handleMultipleEvents(on, elem, type, fn); + } + + var data = Dom.getElData(elem); + + // We need a place to store all our handler data + if (!data.handlers) data.handlers = {}; + + if (!data.handlers[type]) data.handlers[type] = []; + + if (!fn.guid) fn.guid = Guid.newGUID(); + + data.handlers[type].push(fn); + + if (!data.dispatcher) { + data.disabled = false; + + data.dispatcher = function (event, hash) { + + if (data.disabled) return; + event = fixEvent(event); + + var handlers = data.handlers[event.type]; + + if (handlers) { + // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off. + var handlersCopy = handlers.slice(0); + + for (var m = 0, n = handlersCopy.length; m < n; m++) { + if (event.isImmediatePropagationStopped()) { + break; + } else { + handlersCopy[m].call(elem, event, hash); + } + } + } + }; + } + + if (data.handlers[type].length === 1) { + if (elem.addEventListener) { + elem.addEventListener(type, data.dispatcher, false); + } else if (elem.attachEvent) { + elem.attachEvent('on' + type, data.dispatcher); + } + } +} + +/** + * Removes event listeners from an element + * + * @param {Element|Object} elem Object to remove listeners from + * @param {String|Array=} type Type of listener to remove. Don't include to remove all events from element. + * @param {Function} fn Specific listener to remove. Don't include to remove listeners for an event type. + * @method off + */ + +function off(elem, type, fn) { + // Don't want to add a cache object through getElData if not needed + if (!Dom.hasElData(elem)) return; + + var data = Dom.getElData(elem); + + // If no events exist, nothing to unbind + if (!data.handlers) { + return; + } + + if (Array.isArray(type)) { + return _handleMultipleEvents(off, elem, type, fn); + } + + // Utility function + var removeType = function removeType(t) { + data.handlers[t] = []; + _cleanUpEvents(elem, t); + }; + + // Are we removing all bound events? + if (!type) { + for (var t in data.handlers) { + removeType(t); + }return; + } + + var handlers = data.handlers[type]; + + // If no handlers exist, nothing to unbind + if (!handlers) return; + + // If no listener was provided, remove all listeners for type + if (!fn) { + removeType(type); + return; + } + + // We're only removing a single handler + if (fn.guid) { + for (var n = 0; n < handlers.length; n++) { + if (handlers[n].guid === fn.guid) { + handlers.splice(n--, 1); + } + } + } + + _cleanUpEvents(elem, type); +} + +/** + * Trigger an event for an element + * + * @param {Element|Object} elem Element to trigger an event on + * @param {Event|Object|String} event A string (the type) or an event object with a type attribute + * @param {Object} [hash] data hash to pass along with the event + * @return {Boolean=} Returned only if default was prevented + * @method trigger + */ + +function trigger(elem, event, hash) { + // Fetches element data and a reference to the parent (for bubbling). + // Don't want to add a data object to cache for every parent, + // so checking hasElData first. + var elemData = Dom.hasElData(elem) ? Dom.getElData(elem) : {}; + var parent = elem.parentNode || elem.ownerDocument; + // type = event.type || event, + // handler; + + // If an event name was passed as a string, creates an event out of it + if (typeof event === 'string') { + event = { type: event, target: elem }; + } + // Normalizes the event properties. + event = fixEvent(event); + + // If the passed element has a dispatcher, executes the established handlers. + if (elemData.dispatcher) { + elemData.dispatcher.call(elem, event, hash); + } + + // Unless explicitly stopped or the event does not bubble (e.g. media events) + // recursively calls this function to bubble the event up the DOM. + if (parent && !event.isPropagationStopped() && event.bubbles === true) { + trigger.call(null, parent, event, hash); + + // If at the top of the DOM, triggers the default action unless disabled. + } else if (!parent && !event.defaultPrevented) { + var targetData = Dom.getElData(event.target); + + // Checks if the target has a default action for this event. + if (event.target[event.type]) { + // Temporarily disables event dispatching on the target as we have already executed the handler. + targetData.disabled = true; + // Executes the default action. + if (typeof event.target[event.type] === 'function') { + event.target[event.type](); + } + // Re-enables event dispatching. + targetData.disabled = false; + } + } + + // Inform the triggerer if the default was prevented by returning false + return !event.defaultPrevented; +} + +/** + * Trigger a listener only once for an event + * + * @param {Element|Object} elem Element or object to + * @param {String|Array} type Name/type of event + * @param {Function} fn Event handler function + * @method one + */ + +function one(elem, type, fn) { + if (Array.isArray(type)) { + return _handleMultipleEvents(one, elem, type, fn); + } + var func = function func() { + off(elem, type, func); + fn.apply(this, arguments); + }; + // copy the guid to the new function so it can removed using the original function's ID + func.guid = fn.guid = fn.guid || Guid.newGUID(); + on(elem, type, func); +} + +/** + * Fix a native event to have standard property values + * + * @param {Object} event Event object to fix + * @return {Object} + * @private + * @method fixEvent + */ + +function fixEvent(event) { + + function returnTrue() { + return true; + } + function returnFalse() { + return false; + } + + // Test if fixing up is needed + // Used to check if !event.stopPropagation instead of isPropagationStopped + // But native events return true for stopPropagation, but don't have + // other expected methods like isPropagationStopped. Seems to be a problem + // with the Javascript Ninja code. So we're just overriding all events now. + if (!event || !event.isPropagationStopped) { + var old = event || _globalWindow2['default'].event; + + event = {}; + // Clone the old object so that we can modify the values event = {}; + // IE8 Doesn't like when you mess with native event properties + // Firefox returns false for event.hasOwnProperty('type') and other props + // which makes copying more difficult. + // TODO: Probably best to create a whitelist of event props + for (var key in old) { + // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y + // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation + // and webkitMovementX/Y + if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') { + // Chrome 32+ warns if you try to copy deprecated returnValue, but + // we still want to if preventDefault isn't supported (IE8). + if (!(key === 'returnValue' && old.preventDefault)) { + event[key] = old[key]; + } + } + } + + // The event occurred on this element + if (!event.target) { + event.target = event.srcElement || _globalDocument2['default']; + } + + // Handle which other element the event is related to + if (!event.relatedTarget) { + event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; + } + + // Stop the default browser action + event.preventDefault = function () { + if (old.preventDefault) { + old.preventDefault(); + } + event.returnValue = false; + old.returnValue = false; + event.defaultPrevented = true; + }; + + event.defaultPrevented = false; + + // Stop the event from bubbling + event.stopPropagation = function () { + if (old.stopPropagation) { + old.stopPropagation(); + } + event.cancelBubble = true; + old.cancelBubble = true; + event.isPropagationStopped = returnTrue; + }; + + event.isPropagationStopped = returnFalse; + + // Stop the event from bubbling and executing other handlers + event.stopImmediatePropagation = function () { + if (old.stopImmediatePropagation) { + old.stopImmediatePropagation(); + } + event.isImmediatePropagationStopped = returnTrue; + event.stopPropagation(); + }; + + event.isImmediatePropagationStopped = returnFalse; + + // Handle mouse position + if (event.clientX != null) { + var doc = _globalDocument2['default'].documentElement, + body = _globalDocument2['default'].body; + + event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); + event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); + } + + // Handle key presses + event.which = event.charCode || event.keyCode; + + // Fix button for mouse clicks: + // 0 == left; 1 == middle; 2 == right + if (event.button != null) { + event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0; + } + } + + // Returns fixed-up instance + return event; +} + +/** + * Clean up the listener cache and dispatchers +* + * @param {Element|Object} elem Element to clean up + * @param {String} type Type of event to clean up + * @private + * @method _cleanUpEvents + */ +function _cleanUpEvents(elem, type) { + var data = Dom.getElData(elem); + + // Remove the events of a particular type if there are none left + if (data.handlers[type].length === 0) { + delete data.handlers[type]; + // data.handlers[type] = null; + // Setting to null was causing an error with data.handlers + + // Remove the meta-handler from the element + if (elem.removeEventListener) { + elem.removeEventListener(type, data.dispatcher, false); + } else if (elem.detachEvent) { + elem.detachEvent('on' + type, data.dispatcher); + } + } + + // Remove the events object if there are no types left + if (Object.getOwnPropertyNames(data.handlers).length <= 0) { + delete data.handlers; + delete data.dispatcher; + delete data.disabled; + } + + // Finally remove the element data if there is no data left + if (Object.getOwnPropertyNames(data).length === 0) { + Dom.removeElData(elem); + } +} + +/** + * Loops through an array of event types and calls the requested method for each type. + * + * @param {Function} fn The event method we want to use. + * @param {Element|Object} elem Element or object to bind listeners to + * @param {String} type Type of event to bind to. + * @param {Function} callback Event listener. + * @private + * @function _handleMultipleEvents + */ +function _handleMultipleEvents(fn, elem, types, callback) { + types.forEach(function (type) { + //Call the event method for each one of the types + fn(elem, type, callback); + }); +} + +},{"./dom.js":142,"./guid.js":146,"global/document":7,"global/window":8}],144:[function(_dereq_,module,exports){ +/** + * @file fn.js + */ +'use strict'; + +exports.__esModule = true; + +var _guidJs = _dereq_('./guid.js'); + +/** + * Bind (a.k.a proxy or Context). A simple method for changing the context of a function + * It also stores a unique id on the function so it can be easily removed from events + * + * @param {*} context The object to bind as scope + * @param {Function} fn The function to be bound to a scope + * @param {Number=} uid An optional unique ID for the function to be set + * @return {Function} + * @private + * @method bind + */ +var bind = function bind(context, fn, uid) { + // Make sure the function has a unique ID + if (!fn.guid) { + fn.guid = _guidJs.newGUID(); + } + + // Create the new function that changes the context + var ret = function ret() { + return fn.apply(context, arguments); + }; + + // Allow for the ability to individualize this function + // Needed in the case where multiple objects might share the same prototype + // IF both items add an event listener with the same function, then you try to remove just one + // it will remove both because they both have the same guid. + // when using this, you need to use the bind method when you remove the listener as well. + // currently used in text tracks + ret.guid = uid ? uid + '_' + fn.guid : fn.guid; + + return ret; +}; +exports.bind = bind; + +},{"./guid.js":146}],145:[function(_dereq_,module,exports){ +/** + * @file format-time.js + * + * Format seconds as a time string, H:MM:SS or M:SS + * Supplying a guide (in seconds) will force a number of leading zeros + * to cover the length of the guide + * + * @param {Number} seconds Number of seconds to be turned into a string + * @param {Number} guide Number (in seconds) to model the string after + * @return {String} Time formatted as H:MM:SS or M:SS + * @private + * @function formatTime + */ +'use strict'; + +exports.__esModule = true; +function formatTime(seconds) { + var guide = arguments.length <= 1 || arguments[1] === undefined ? seconds : arguments[1]; + return (function () { + seconds = seconds < 0 ? 0 : seconds; + var s = Math.floor(seconds % 60); + var m = Math.floor(seconds / 60 % 60); + var h = Math.floor(seconds / 3600); + var gm = Math.floor(guide / 60 % 60); + var gh = Math.floor(guide / 3600); + + // handle invalid times + if (isNaN(seconds) || seconds === Infinity) { + // '-' is false for all relational operators (e.g. <, >=) so this setting + // will add the minimum number of fields specified by the guide + h = m = s = '-'; + } + + // Check if we need to show hours + h = h > 0 || gh > 0 ? h + ':' : ''; + + // If hours are showing, we may need to add a leading zero. + // Always show at least one digit of minutes. + m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':'; + + // Check if leading zero is need for seconds + s = s < 10 ? '0' + s : s; + + return h + m + s; + })(); +} + +exports['default'] = formatTime; +module.exports = exports['default']; + +},{}],146:[function(_dereq_,module,exports){ +/** + * @file guid.js + * + * Unique ID for an element or function + * @type {Number} + * @private + */ +"use strict"; + +exports.__esModule = true; +exports.newGUID = newGUID; +var _guid = 1; + +/** + * Get the next unique ID + * + * @return {String} + * @function newGUID + */ + +function newGUID() { + return _guid++; +} + +},{}],147:[function(_dereq_,module,exports){ +/** + * @file log.js + */ +'use strict'; + +exports.__esModule = true; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _globalWindow = _dereq_('global/window'); + +var _globalWindow2 = _interopRequireDefault(_globalWindow); + +/** + * Log plain debug messages + */ +var log = function log() { + _logType(null, arguments); +}; + +/** + * Keep a history of log messages + * @type {Array} + */ +log.history = []; + +/** + * Log error messages + */ +log.error = function () { + _logType('error', arguments); +}; + +/** + * Log warning messages + */ +log.warn = function () { + _logType('warn', arguments); +}; + +/** + * Log messages to the console and history based on the type of message + * + * @param {String} type The type of message, or `null` for `log` + * @param {Object} args The args to be passed to the log + * @private + * @method _logType + */ +function _logType(type, args) { + // convert args to an array to get array functions + var argsArray = Array.prototype.slice.call(args); + // if there's no console then don't try to output messages + // they will still be stored in log.history + // Was setting these once outside of this function, but containing them + // in the function makes it easier to test cases where console doesn't exist + var noop = function noop() {}; + + var console = _globalWindow2['default']['console'] || { + 'log': noop, + 'warn': noop, + 'error': noop + }; + + if (type) { + // add the type to the front of the message + argsArray.unshift(type.toUpperCase() + ':'); + } else { + // default to log with no prefix + type = 'log'; + } + + // add to history + log.history.push(argsArray); + + // add console prefix after adding to history + argsArray.unshift('VIDEOJS:'); + + // call appropriate log function + if (console[type].apply) { + console[type].apply(console, argsArray); + } else { + // ie8 doesn't allow error.apply, but it will just join() the array anyway + console[type](argsArray.join(' ')); + } +} + +exports['default'] = log; +module.exports = exports['default']; + +},{"global/window":8}],148:[function(_dereq_,module,exports){ +/** + * @file merge-options.js + */ +'use strict'; + +exports.__esModule = true; +exports['default'] = mergeOptions; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _lodashCompatObjectMerge = _dereq_('lodash-compat/object/merge'); + +var _lodashCompatObjectMerge2 = _interopRequireDefault(_lodashCompatObjectMerge); + +function isPlain(obj) { + return !!obj && typeof obj === 'object' && obj.toString() === '[object Object]' && obj.constructor === Object; +} + +/** + * Merge customizer. video.js simply overwrites non-simple objects + * (like arrays) instead of attempting to overlay them. + * @see https://lodash.com/docs#merge + */ +var customizer = function customizer(destination, source) { + // If we're not working with a plain object, copy the value as is + // If source is an array, for instance, it will replace destination + if (!isPlain(source)) { + return source; + } + + // If the new value is a plain object but the first object value is not + // we need to create a new object for the first object to merge with. + // This makes it consistent with how merge() works by default + // and also protects from later changes the to first object affecting + // the second object's values. + if (!isPlain(destination)) { + return mergeOptions(source); + } +}; + +/** + * Merge one or more options objects, recursively merging **only** + * plain object properties. Previously `deepMerge`. + * + * @param {...Object} source One or more objects to merge + * @returns {Object} a new object that is the union of all + * provided objects + * @function mergeOptions + */ + +function mergeOptions() { + // contruct the call dynamically to handle the variable number of + // objects to merge + var args = Array.prototype.slice.call(arguments); + + // unshift an empty object into the front of the call as the target + // of the merge + args.unshift({}); + + // customize conflict resolution to match our historical merge behavior + args.push(customizer); + + _lodashCompatObjectMerge2['default'].apply(null, args); + + // return the mutated result object + return args[0]; +} + +module.exports = exports['default']; + +},{"lodash-compat/object/merge":46}],149:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _globalDocument = _dereq_('global/document'); + +var _globalDocument2 = _interopRequireDefault(_globalDocument); + +var createStyleElement = function createStyleElement(className) { + var style = _globalDocument2['default'].createElement('style'); + style.className = className; + + return style; +}; + +exports.createStyleElement = createStyleElement; +var setTextContent = function setTextContent(el, content) { + if (el.styleSheet) { + el.styleSheet.cssText = content; + } else { + el.textContent = content; + } +}; +exports.setTextContent = setTextContent; + +},{"global/document":7}],150:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; +exports.createTimeRanges = createTimeRanges; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _logJs = _dereq_('./log.js'); + +var _logJs2 = _interopRequireDefault(_logJs); + +/** + * @file time-ranges.js + * + * Should create a fake TimeRange object + * Mimics an HTML5 time range instance, which has functions that + * return the start and end times for a range + * TimeRanges are returned by the buffered() method + * + * @param {(Number|Array)} Start of a single range or an array of ranges + * @param {Number} End of a single range + * @private + * @method createTimeRanges + */ + +function createTimeRanges(start, end) { + if (Array.isArray(start)) { + return createTimeRangesObj(start); + } else if (start === undefined || end === undefined) { + return createTimeRangesObj(); + } + return createTimeRangesObj([[start, end]]); +} + +exports.createTimeRange = createTimeRanges; + +function createTimeRangesObj(ranges) { + if (ranges === undefined || ranges.length === 0) { + return { + length: 0, + start: function start() { + throw new Error('This TimeRanges object is empty'); + }, + end: function end() { + throw new Error('This TimeRanges object is empty'); + } + }; + } + return { + length: ranges.length, + start: getRange.bind(null, 'start', 0, ranges), + end: getRange.bind(null, 'end', 1, ranges) + }; +} + +function getRange(fnName, valueIndex, ranges, rangeIndex) { + if (rangeIndex === undefined) { + _logJs2['default'].warn('DEPRECATED: Function \'' + fnName + '\' on \'TimeRanges\' called without an index argument.'); + rangeIndex = 0; + } + rangeCheck(fnName, rangeIndex, ranges.length - 1); + return ranges[rangeIndex][valueIndex]; +} + +function rangeCheck(fnName, index, maxIndex) { + if (index < 0 || index > maxIndex) { + throw new Error('Failed to execute \'' + fnName + '\' on \'TimeRanges\': The index provided (' + index + ') is greater than or equal to the maximum bound (' + maxIndex + ').'); + } +} + +},{"./log.js":147}],151:[function(_dereq_,module,exports){ +/** + * @file to-title-case.js + * + * Uppercase the first letter of a string + * + * @param {String} string String to be uppercased + * @return {String} + * @private + * @method toTitleCase + */ +"use strict"; + +exports.__esModule = true; +function toTitleCase(string) { + return string.charAt(0).toUpperCase() + string.slice(1); +} + +exports["default"] = toTitleCase; +module.exports = exports["default"]; + +},{}],152:[function(_dereq_,module,exports){ +/** + * @file url.js + */ +'use strict'; + +exports.__esModule = true; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _globalDocument = _dereq_('global/document'); + +var _globalDocument2 = _interopRequireDefault(_globalDocument); + +var _globalWindow = _dereq_('global/window'); + +var _globalWindow2 = _interopRequireDefault(_globalWindow); + +/** + * Resolve and parse the elements of a URL + * + * @param {String} url The url to parse + * @return {Object} An object of url details + * @method parseUrl + */ +var parseUrl = function parseUrl(url) { + var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host']; + + // add the url to an anchor and let the browser parse the URL + var a = _globalDocument2['default'].createElement('a'); + a.href = url; + + // IE8 (and 9?) Fix + // ie8 doesn't parse the URL correctly until the anchor is actually + // added to the body, and an innerHTML is needed to trigger the parsing + var addToBody = a.host === '' && a.protocol !== 'file:'; + var div = undefined; + if (addToBody) { + div = _globalDocument2['default'].createElement('div'); + div.innerHTML = ''; + a = div.firstChild; + // prevent the div from affecting layout + div.setAttribute('style', 'display:none; position:absolute;'); + _globalDocument2['default'].body.appendChild(div); + } + + // Copy the specific URL properties to a new object + // This is also needed for IE8 because the anchor loses its + // properties when it's removed from the dom + var details = {}; + for (var i = 0; i < props.length; i++) { + details[props[i]] = a[props[i]]; + } + + // IE9 adds the port to the host property unlike everyone else. If + // a port identifier is added for standard ports, strip it. + if (details.protocol === 'http:') { + details.host = details.host.replace(/:80$/, ''); + } + if (details.protocol === 'https:') { + details.host = details.host.replace(/:443$/, ''); + } + + if (addToBody) { + _globalDocument2['default'].body.removeChild(div); + } + + return details; +}; + +exports.parseUrl = parseUrl; +/** + * Get absolute version of relative URL. Used to tell flash correct URL. + * http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue + * + * @param {String} url URL to make absolute + * @return {String} Absolute URL + * @private + * @method getAbsoluteURL + */ +var getAbsoluteURL = function getAbsoluteURL(url) { + // Check if absolute URL + if (!url.match(/^https?:\/\//)) { + // Convert to absolute URL. Flash hosted off-site needs an absolute URL. + var div = _globalDocument2['default'].createElement('div'); + div.innerHTML = 'x'; + url = div.firstChild.href; + } + + return url; +}; + +exports.getAbsoluteURL = getAbsoluteURL; +/** + * Returns the extension of the passed file name. It will return an empty string if you pass an invalid path + * + * @param {String} path The fileName path like '/path/to/file.mp4' + * @returns {String} The extension in lower case or an empty string if no extension could be found. + * @method getFileExtension + */ +var getFileExtension = function getFileExtension(path) { + if (typeof path === 'string') { + var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/i; + var pathParts = splitPathRe.exec(path); + + if (pathParts) { + return pathParts.pop().toLowerCase(); + } + } + + return ''; +}; + +exports.getFileExtension = getFileExtension; +/** + * Returns whether the url passed is a cross domain request or not. + * + * @param {String} url The url to check + * @return {Boolean} Whether it is a cross domain request or not + * @method isCrossOrigin + */ +var isCrossOrigin = function isCrossOrigin(url) { + var winLoc = _globalWindow2['default'].location; + var urlInfo = parseUrl(url); + + // IE8 protocol relative urls will return ':' for protocol + var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol; + + // Check if url is for another domain/origin + // IE8 doesn't know location.origin, so we won't rely on it here + var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host; + + return crossOrigin; +}; +exports.isCrossOrigin = isCrossOrigin; + +},{"global/document":7,"global/window":8}],153:[function(_dereq_,module,exports){ +/** + * @file video.js + */ +'use strict'; + +exports.__esModule = true; + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _globalWindow = _dereq_('global/window'); + +var _globalWindow2 = _interopRequireDefault(_globalWindow); + +var _globalDocument = _dereq_('global/document'); + +var _globalDocument2 = _interopRequireDefault(_globalDocument); + +var _setup = _dereq_('./setup'); + +var setup = _interopRequireWildcard(_setup); + +var _utilsStylesheetJs = _dereq_('./utils/stylesheet.js'); + +var stylesheet = _interopRequireWildcard(_utilsStylesheetJs); + +var _component = _dereq_('./component'); + +var _component2 = _interopRequireDefault(_component); + +var _eventTarget = _dereq_('./event-target'); + +var _eventTarget2 = _interopRequireDefault(_eventTarget); + +var _utilsEventsJs = _dereq_('./utils/events.js'); + +var Events = _interopRequireWildcard(_utilsEventsJs); + +var _player = _dereq_('./player'); + +var _player2 = _interopRequireDefault(_player); + +var _pluginsJs = _dereq_('./plugins.js'); + +var _pluginsJs2 = _interopRequireDefault(_pluginsJs); + +var _srcJsUtilsMergeOptionsJs = _dereq_('../../src/js/utils/merge-options.js'); + +var _srcJsUtilsMergeOptionsJs2 = _interopRequireDefault(_srcJsUtilsMergeOptionsJs); + +var _utilsFnJs = _dereq_('./utils/fn.js'); + +var Fn = _interopRequireWildcard(_utilsFnJs); + +var _tracksTextTrackJs = _dereq_('./tracks/text-track.js'); + +var _tracksTextTrackJs2 = _interopRequireDefault(_tracksTextTrackJs); + +var _tracksAudioTrackJs = _dereq_('./tracks/audio-track.js'); + +var _tracksAudioTrackJs2 = _interopRequireDefault(_tracksAudioTrackJs); + +var _tracksVideoTrackJs = _dereq_('./tracks/video-track.js'); + +var _tracksVideoTrackJs2 = _interopRequireDefault(_tracksVideoTrackJs); + +var _utilsTimeRangesJs = _dereq_('./utils/time-ranges.js'); + +var _utilsFormatTimeJs = _dereq_('./utils/format-time.js'); + +var _utilsFormatTimeJs2 = _interopRequireDefault(_utilsFormatTimeJs); + +var _utilsLogJs = _dereq_('./utils/log.js'); + +var _utilsLogJs2 = _interopRequireDefault(_utilsLogJs); + +var _utilsDomJs = _dereq_('./utils/dom.js'); + +var Dom = _interopRequireWildcard(_utilsDomJs); + +var _utilsBrowserJs = _dereq_('./utils/browser.js'); + +var browser = _interopRequireWildcard(_utilsBrowserJs); + +var _utilsUrlJs = _dereq_('./utils/url.js'); + +var Url = _interopRequireWildcard(_utilsUrlJs); + +var _extendJs = _dereq_('./extend.js'); + +var _extendJs2 = _interopRequireDefault(_extendJs); + +var _lodashCompatObjectMerge = _dereq_('lodash-compat/object/merge'); + +var _lodashCompatObjectMerge2 = _interopRequireDefault(_lodashCompatObjectMerge); + +var _xhr = _dereq_('xhr'); + +var _xhr2 = _interopRequireDefault(_xhr); + +// Include the built-in techs + +var _techTechJs = _dereq_('./tech/tech.js'); + +var _techTechJs2 = _interopRequireDefault(_techTechJs); + +var _techHtml5Js = _dereq_('./tech/html5.js'); + +var _techHtml5Js2 = _interopRequireDefault(_techHtml5Js); + +var _techFlashJs = _dereq_('./tech/flash.js'); + +var _techFlashJs2 = _interopRequireDefault(_techFlashJs); + +// HTML5 Element Shim for IE8 +if (typeof HTMLVideoElement === 'undefined') { + _globalDocument2['default'].createElement('video'); + _globalDocument2['default'].createElement('audio'); + _globalDocument2['default'].createElement('track'); +} + +/** + * Doubles as the main function for users to create a player instance and also + * the main library object. + * The `videojs` function can be used to initialize or retrieve a player. + * ```js + * var myPlayer = videojs('my_video_id'); + * ``` + * + * @param {String|Element} id Video element or video element ID + * @param {Object=} options Optional options object for config/settings + * @param {Function=} ready Optional ready callback + * @return {Player} A player instance + * @mixes videojs + * @method videojs + */ +function videojs(id, options, ready) { + var tag = undefined; // Element of ID + + // Allow for element or ID to be passed in + // String ID + if (typeof id === 'string') { + + // Adjust for jQuery ID syntax + if (id.indexOf('#') === 0) { + id = id.slice(1); + } + + // If a player instance has already been created for this ID return it. + if (videojs.getPlayers()[id]) { + + // If options or ready funtion are passed, warn + if (options) { + _utilsLogJs2['default'].warn('Player "' + id + '" is already initialised. Options will not be applied.'); + } + + if (ready) { + videojs.getPlayers()[id].ready(ready); + } + + return videojs.getPlayers()[id]; + + // Otherwise get element for ID + } else { + tag = Dom.getEl(id); + } + + // ID is a media element + } else { + tag = id; + } + + // Check for a useable element + if (!tag || !tag.nodeName) { + // re: nodeName, could be a box div also + throw new TypeError('The element or ID supplied is not valid. (videojs)'); // Returns + } + + // Element may have a player attr referring to an already created player instance. + // If not, set up a new player and return the instance. + return tag['player'] || _player2['default'].players[tag.playerId] || new _player2['default'](tag, options, ready); +} + +// Add default styles +if (_globalWindow2['default'].VIDEOJS_NO_DYNAMIC_STYLE !== true) { + var style = Dom.$('.vjs-styles-defaults'); + + if (!style) { + style = stylesheet.createStyleElement('vjs-styles-defaults'); + var head = Dom.$('head'); + head.insertBefore(style, head.firstChild); + stylesheet.setTextContent(style, '\n .video-js {\n width: 300px;\n height: 150px;\n }\n\n .vjs-fluid {\n padding-top: 56.25%\n }\n '); + } +} + +// Run Auto-load players +// 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) +setup.autoSetupTimeout(1, videojs); + +/* + * Current software version (semver) + * + * @type {String} + */ +videojs.VERSION = '5.10.7'; + +/** + * The global options object. These are the settings that take effect + * if no overrides are specified when the player is created. + * + * ```js + * videojs.options.autoplay = true + * // -> all players will autoplay by default + * ``` + * + * @type {Object} + */ +videojs.options = _player2['default'].prototype.options_; + +/** + * Get an object with the currently created players, keyed by player ID + * + * @return {Object} The created players + * @mixes videojs + * @method getPlayers + */ +videojs.getPlayers = function () { + return _player2['default'].players; +}; + +/** + * Expose players object. + * + * @memberOf videojs + * @property {Object} players + */ +videojs.players = _player2['default'].players; + +/** + * Get a component class object by name + * ```js + * var VjsButton = videojs.getComponent('Button'); + * // Create a new instance of the component + * var myButton = new VjsButton(myPlayer); + * ``` + * + * @return {Component} Component identified by name + * @mixes videojs + * @method getComponent + */ +videojs.getComponent = _component2['default'].getComponent; + +/** + * Register a component so it can referred to by name + * Used when adding to other + * components, either through addChild + * `component.addChild('myComponent')` + * or through default children options + * `{ children: ['myComponent'] }`. + * ```js + * // Get a component to subclass + * var VjsButton = videojs.getComponent('Button'); + * // Subclass the component (see 'extend' doc for more info) + * var MySpecialButton = videojs.extend(VjsButton, {}); + * // Register the new component + * VjsButton.registerComponent('MySepcialButton', MySepcialButton); + * // (optionally) add the new component as a default player child + * myPlayer.addChild('MySepcialButton'); + * ``` + * NOTE: You could also just initialize the component before adding. + * `component.addChild(new MyComponent());` + * + * @param {String} The class name of the component + * @param {Component} The component class + * @return {Component} The newly registered component + * @mixes videojs + * @method registerComponent + */ +videojs.registerComponent = function (name, comp) { + if (_techTechJs2['default'].isTech(comp)) { + _utilsLogJs2['default'].warn('The ' + name + ' tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)'); + } + + _component2['default'].registerComponent.call(_component2['default'], name, comp); +}; + +/** + * Get a Tech class object by name + * ```js + * var Html5 = videojs.getTech('Html5'); + * // Create a new instance of the component + * var html5 = new Html5(options); + * ``` + * + * @return {Tech} Tech identified by name + * @mixes videojs + * @method getComponent + */ +videojs.getTech = _techTechJs2['default'].getTech; + +/** + * Register a Tech so it can referred to by name. + * This is used in the tech order for the player. + * + * ```js + * // get the Html5 Tech + * var Html5 = videojs.getTech('Html5'); + * var MyTech = videojs.extend(Html5, {}); + * // Register the new Tech + * VjsButton.registerTech('Tech', MyTech); + * var player = videojs('myplayer', { + * techOrder: ['myTech', 'html5'] + * }); + * ``` + * + * @param {String} The class name of the tech + * @param {Tech} The tech class + * @return {Tech} The newly registered Tech + * @mixes videojs + * @method registerTech + */ +videojs.registerTech = _techTechJs2['default'].registerTech; + +/** + * A suite of browser and device tests + * + * @type {Object} + * @private + */ +videojs.browser = browser; + +/** + * Whether or not the browser supports touch events. Included for backward + * compatibility with 4.x, but deprecated. Use `videojs.browser.TOUCH_ENABLED` + * instead going forward. + * + * @deprecated + * @type {Boolean} + */ +videojs.TOUCH_ENABLED = browser.TOUCH_ENABLED; + +/** + * Subclass an existing class + * Mimics ES6 subclassing with the `extend` keyword + * ```js + * // Create a basic javascript 'class' + * function MyClass(name){ + * // Set a property at initialization + * this.myName = name; + * } + * // Create an instance method + * MyClass.prototype.sayMyName = function(){ + * alert(this.myName); + * }; + * // Subclass the exisitng class and change the name + * // when initializing + * var MySubClass = videojs.extend(MyClass, { + * constructor: function(name) { + * // Call the super class constructor for the subclass + * MyClass.call(this, name) + * } + * }); + * // Create an instance of the new sub class + * var myInstance = new MySubClass('John'); + * myInstance.sayMyName(); // -> should alert "John" + * ``` + * + * @param {Function} The Class to subclass + * @param {Object} An object including instace methods for the new class + * Optionally including a `constructor` function + * @return {Function} The newly created subclass + * @mixes videojs + * @method extend + */ +videojs.extend = _extendJs2['default']; + +/** + * Merge two options objects recursively + * Performs a deep merge like lodash.merge but **only merges plain objects** + * (not arrays, elements, anything else) + * Other values will be copied directly from the second object. + * ```js + * var defaultOptions = { + * foo: true, + * bar: { + * a: true, + * b: [1,2,3] + * } + * }; + * var newOptions = { + * foo: false, + * bar: { + * b: [4,5,6] + * } + * }; + * var result = videojs.mergeOptions(defaultOptions, newOptions); + * // result.foo = false; + * // result.bar.a = true; + * // result.bar.b = [4,5,6]; + * ``` + * + * @param {Object} defaults The options object whose values will be overriden + * @param {Object} overrides The options object with values to override the first + * @param {Object} etc Any number of additional options objects + * + * @return {Object} a new object with the merged values + * @mixes videojs + * @method mergeOptions + */ +videojs.mergeOptions = _srcJsUtilsMergeOptionsJs2['default']; + +/** + * Change the context (this) of a function + * + * videojs.bind(newContext, function(){ + * this === newContext + * }); + * + * NOTE: as of v5.0 we require an ES5 shim, so you should use the native + * `function(){}.bind(newContext);` instead of this. + * + * @param {*} context The object to bind as scope + * @param {Function} fn The function to be bound to a scope + * @param {Number=} uid An optional unique ID for the function to be set + * @return {Function} + */ +videojs.bind = Fn.bind; + +/** + * Create a Video.js player plugin + * Plugins are only initialized when options for the plugin are included + * in the player options, or the plugin function on the player instance is + * called. + * **See the plugin guide in the docs for a more detailed example** + * ```js + * // Make a plugin that alerts when the player plays + * videojs.plugin('myPlugin', function(myPluginOptions) { + * myPluginOptions = myPluginOptions || {}; + * + * var player = this; + * var alertText = myPluginOptions.text || 'Player is playing!' + * + * player.on('play', function(){ + * alert(alertText); + * }); + * }); + * // USAGE EXAMPLES + * // EXAMPLE 1: New player with plugin options, call plugin immediately + * var player1 = videojs('idOne', { + * myPlugin: { + * text: 'Custom text!' + * } + * }); + * // Click play + * // --> Should alert 'Custom text!' + * // EXAMPLE 3: New player, initialize plugin later + * var player3 = videojs('idThree'); + * // Click play + * // --> NO ALERT + * // Click pause + * // Initialize plugin using the plugin function on the player instance + * player3.myPlugin({ + * text: 'Plugin added later!' + * }); + * // Click play + * // --> Should alert 'Plugin added later!' + * ``` + * + * @param {String} name The plugin name + * @param {Function} fn The plugin function that will be called with options + * @mixes videojs + * @method plugin + */ +videojs.plugin = _pluginsJs2['default']; + +/** + * Adding languages so that they're available to all players. + * ```js + * videojs.addLanguage('es', { 'Hello': 'Hola' }); + * ``` + * + * @param {String} code The language code or dictionary property + * @param {Object} data The data values to be translated + * @return {Object} The resulting language dictionary object + * @mixes videojs + * @method addLanguage + */ +videojs.addLanguage = function (code, data) { + var _merge; + + code = ('' + code).toLowerCase(); + return _lodashCompatObjectMerge2['default'](videojs.options.languages, (_merge = {}, _merge[code] = data, _merge))[code]; +}; + +/** + * Log debug messages. + * + * @param {...Object} messages One or more messages to log + */ +videojs.log = _utilsLogJs2['default']; + +/** + * Creates an emulated TimeRange object. + * + * @param {Number|Array} start Start time in seconds or an array of ranges + * @param {Number} end End time in seconds + * @return {Object} Fake TimeRange object + * @method createTimeRange + */ +videojs.createTimeRange = videojs.createTimeRanges = _utilsTimeRangesJs.createTimeRanges; + +/** + * Format seconds as a time string, H:MM:SS or M:SS + * Supplying a guide (in seconds) will force a number of leading zeros + * to cover the length of the guide + * + * @param {Number} seconds Number of seconds to be turned into a string + * @param {Number} guide Number (in seconds) to model the string after + * @return {String} Time formatted as H:MM:SS or M:SS + * @method formatTime + */ +videojs.formatTime = _utilsFormatTimeJs2['default']; + +/** + * Resolve and parse the elements of a URL + * + * @param {String} url The url to parse + * @return {Object} An object of url details + * @method parseUrl + */ +videojs.parseUrl = Url.parseUrl; + +/** + * Returns whether the url passed is a cross domain request or not. + * + * @param {String} url The url to check + * @return {Boolean} Whether it is a cross domain request or not + * @method isCrossOrigin + */ +videojs.isCrossOrigin = Url.isCrossOrigin; + +/** + * Event target class. + * + * @type {Function} + */ +videojs.EventTarget = _eventTarget2['default']; + +/** + * Add an event listener to element + * It stores the handler function in a separate cache object + * and adds a generic handler to the element's event, + * along with a unique id (guid) to the element. + * + * @param {Element|Object} elem Element or object to bind listeners to + * @param {String|Array} type Type of event to bind to. + * @param {Function} fn Event listener. + * @method on + */ +videojs.on = Events.on; + +/** + * Trigger a listener only once for an event + * + * @param {Element|Object} elem Element or object to + * @param {String|Array} type Name/type of event + * @param {Function} fn Event handler function + * @method one + */ +videojs.one = Events.one; + +/** + * Removes event listeners from an element + * + * @param {Element|Object} elem Object to remove listeners from + * @param {String|Array=} type Type of listener to remove. Don't include to remove all events from element. + * @param {Function} fn Specific listener to remove. Don't include to remove listeners for an event type. + * @method off + */ +videojs.off = Events.off; + +/** + * Trigger an event for an element + * + * @param {Element|Object} elem Element to trigger an event on + * @param {Event|Object|String} event A string (the type) or an event object with a type attribute + * @param {Object} [hash] data hash to pass along with the event + * @return {Boolean=} Returned only if default was prevented + * @method trigger + */ +videojs.trigger = Events.trigger; + +/** + * A cross-browser XMLHttpRequest wrapper. Here's a simple example: + * + * videojs.xhr({ + * body: someJSONString, + * uri: "/foo", + * headers: { + * "Content-Type": "application/json" + * } + * }, function (err, resp, body) { + * // check resp.statusCode + * }); + * + * Check out the [full + * documentation](https://github.com/Raynos/xhr/blob/v2.1.0/README.md) + * for more options. + * + * @param {Object} options settings for the request. + * @return {XMLHttpRequest|XDomainRequest} the request object. + * @see https://github.com/Raynos/xhr + */ +videojs.xhr = _xhr2['default']; + +/** + * TextTrack class + * + * @type {Function} + */ +videojs.TextTrack = _tracksTextTrackJs2['default']; + +/** + * export the AudioTrack class so that source handlers can create + * AudioTracks and then add them to the players AudioTrackList + * + * @type {Function} + */ +videojs.AudioTrack = _tracksAudioTrackJs2['default']; + +/** + * export the VideoTrack class so that source handlers can create + * VideoTracks and then add them to the players VideoTrackList + * + * @type {Function} + */ +videojs.VideoTrack = _tracksVideoTrackJs2['default']; + +/** + * Determines, via duck typing, whether or not a value is a DOM element. + * + * @method isEl + * @param {Mixed} value + * @return {Boolean} + */ +videojs.isEl = Dom.isEl; + +/** + * Determines, via duck typing, whether or not a value is a text node. + * + * @method isTextNode + * @param {Mixed} value + * @return {Boolean} + */ +videojs.isTextNode = Dom.isTextNode; + +/** + * Creates an element and applies properties. + * + * @method createEl + * @param {String} [tagName='div'] Name of tag to be created. + * @param {Object} [properties={}] Element properties to be applied. + * @param {Object} [attributes={}] Element attributes to be applied. + * @return {Element} + */ +videojs.createEl = Dom.createEl; + +/** + * Check if an element has a CSS class + * + * @method hasClass + * @param {Element} element Element to check + * @param {String} classToCheck Classname to check + */ +videojs.hasClass = Dom.hasElClass; + +/** + * Add a CSS class name to an element + * + * @method addClass + * @param {Element} element Element to add class name to + * @param {String} classToAdd Classname to add + */ +videojs.addClass = Dom.addElClass; + +/** + * Remove a CSS class name from an element + * + * @method removeClass + * @param {Element} element Element to remove from class name + * @param {String} classToRemove Classname to remove + */ +videojs.removeClass = Dom.removeElClass; + +/** + * Adds or removes a CSS class name on an element depending on an optional + * condition or the presence/absence of the class name. + * + * @method toggleElClass + * @param {Element} element + * @param {String} classToToggle + * @param {Boolean|Function} [predicate] + * Can be a function that returns a Boolean. If `true`, the class + * will be added; if `false`, the class will be removed. If not + * given, the class will be added if not present and vice versa. + */ +videojs.toggleClass = Dom.toggleElClass; + +/** + * Apply attributes to an HTML element. + * + * @method setAttributes + * @param {Element} el Target element. + * @param {Object=} attributes Element attributes to be applied. + */ +videojs.setAttributes = Dom.setElAttributes; + +/** + * Get an element's attribute values, as defined on the HTML tag + * Attributes are not the same as properties. They're defined on the tag + * or with setAttribute (which shouldn't be used with HTML) + * This will return true or false for boolean attributes. + * + * @method getAttributes + * @param {Element} tag Element from which to get tag attributes + * @return {Object} + */ +videojs.getAttributes = Dom.getElAttributes; + +/** + * Empties the contents of an element. + * + * @method emptyEl + * @param {Element} el + * @return {Element} + */ +videojs.emptyEl = Dom.emptyEl; + +/** + * Normalizes and appends content to an element. + * + * The content for an element can be passed in multiple types and + * combinations, whose behavior is as follows: + * + * - String + * Normalized into a text node. + * + * - Element, TextNode + * Passed through. + * + * - Array + * A one-dimensional array of strings, elements, nodes, or functions (which + * return single strings, elements, or nodes). + * + * - Function + * If the sole argument, is expected to produce a string, element, + * node, or array. + * + * @method appendContent + * @param {Element} el + * @param {String|Element|TextNode|Array|Function} content + * @return {Element} + */ +videojs.appendContent = Dom.appendContent; + +/** + * Normalizes and inserts content into an element; this is identical to + * `appendContent()`, except it empties the element first. + * + * The content for an element can be passed in multiple types and + * combinations, whose behavior is as follows: + * + * - String + * Normalized into a text node. + * + * - Element, TextNode + * Passed through. + * + * - Array + * A one-dimensional array of strings, elements, nodes, or functions (which + * return single strings, elements, or nodes). + * + * - Function + * If the sole argument, is expected to produce a string, element, + * node, or array. + * + * @method insertContent + * @param {Element} el + * @param {String|Element|TextNode|Array|Function} content + * @return {Element} + */ +videojs.insertContent = Dom.insertContent; + +/* + * Custom Universal Module Definition (UMD) + * + * Video.js will never be a non-browser lib so we can simplify UMD a bunch and + * still support requirejs and browserify. This also needs to be closure + * compiler compatible, so string keys are used. + */ +if (typeof define === 'function' && define['amd']) { + define('videojs', [], function () { + return videojs; + }); + + // checking that module is an object too because of umdjs/umd#35 +} else if (typeof exports === 'object' && typeof module === 'object') { + module['exports'] = videojs; + } + +exports['default'] = videojs; +module.exports = exports['default']; + +},{"../../src/js/utils/merge-options.js":148,"./component":67,"./event-target":104,"./extend.js":105,"./player":113,"./plugins.js":114,"./setup":118,"./tech/flash.js":121,"./tech/html5.js":122,"./tech/tech.js":124,"./tracks/audio-track.js":126,"./tracks/text-track.js":134,"./tracks/video-track.js":139,"./utils/browser.js":140,"./utils/dom.js":142,"./utils/events.js":143,"./utils/fn.js":144,"./utils/format-time.js":145,"./utils/log.js":147,"./utils/stylesheet.js":149,"./utils/time-ranges.js":150,"./utils/url.js":152,"global/document":7,"global/window":8,"lodash-compat/object/merge":46,"xhr":61}]},{},[153])(153) +}); + + +//# sourceMappingURL=video.js.map /* vtt.js - v0.12.1 (https://github.com/mozilla/vtt.js) built on 08-07-2015 */ (function(root) { diff --git a/vendor/assets/javascripts/videojs-contrib-hls.js b/vendor/assets/javascripts/videojs-contrib-hls.js new file mode 100644 index 0000000..5a07bdb --- /dev/null +++ b/vendor/assets/javascripts/videojs-contrib-hls.js @@ -0,0 +1,14799 @@ +/** + * videojs-contrib-hls + * @version 3.1.0 + * @copyright 2016 Brightcove, Inc + * @license Apache-2.0 + */ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.videojsContribHls = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 0x20 && e < 0x7e) { + return String.fromCharCode(e); + } + return '.'; +}; + +/** + * utils to help dump binary data to the console + */ +var utils = { + hexDump: function hexDump(data) { + var bytes = Array.prototype.slice.call(data); + var step = 16; + var result = ''; + var hex = undefined; + var ascii = undefined; + + for (var j = 0; j < bytes.length / step; j++) { + hex = bytes.slice(j * step, j * step + step).map(formatHexString).join(''); + ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join(''); + result += hex + ' ' + ascii + '\n'; + } + return result; + }, + tagDump: function tagDump(tag) { + return utils.hexDump(tag.bytes); + }, + textRanges: function textRanges(ranges) { + var result = ''; + var i = undefined; + + for (i = 0; i < ranges.length; i++) { + result += textRange(ranges, i) + ' '; + } + return result; + } +}; + +exports['default'] = utils; +module.exports = exports['default']; +},{}],2:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports["default"] = { + GOAL_BUFFER_LENGTH: 30 +}; +module.exports = exports["default"]; +},{}],3:[function(require,module,exports){ +/** + * @file decrypter/aes.js + * + * This file contains an adaptation of the AES decryption algorithm + * from the Standford Javascript Cryptography Library. That work is + * covered by the following copyright and permissions notice: + * + * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation + * are those of the authors and should not be interpreted as representing + * official policies, either expressed or implied, of the authors. + */ + +/** + * Expand the S-box tables. + * + * @private + */ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +var precompute = function precompute() { + var tables = [[[], [], [], [], []], [[], [], [], [], []]]; + var encTable = tables[0]; + var decTable = tables[1]; + var sbox = encTable[4]; + var sboxInv = decTable[4]; + var i = undefined; + var x = undefined; + var xInv = undefined; + var d = []; + var th = []; + var x2 = undefined; + var x4 = undefined; + var x8 = undefined; + var s = undefined; + var tEnc = undefined; + var tDec = undefined; + + // Compute double and third tables + for (i = 0; i < 256; i++) { + th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i; + } + + for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) { + // Compute sbox + s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4; + s = s >> 8 ^ s & 255 ^ 99; + sbox[x] = s; + sboxInv[s] = x; + + // Compute MixColumns + x8 = d[x4 = d[x2 = d[x]]]; + tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100; + tEnc = d[s] * 0x101 ^ s * 0x1010100; + + for (i = 0; i < 4; i++) { + encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8; + decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8; + } + } + + // Compactify. Considerable speedup on Firefox. + for (i = 0; i < 5; i++) { + encTable[i] = encTable[i].slice(0); + decTable[i] = decTable[i].slice(0); + } + return tables; +}; +var aesTables = null; + +/** + * Schedule out an AES key for both encryption and decryption. This + * is a low-level class. Use a cipher mode to do bulk encryption. + * + * @class AES + * @param key {Array} The key as an array of 4, 6 or 8 words. + */ + +var AES = (function () { + function AES(key) { + _classCallCheck(this, AES); + + /** + * The expanded S-box and inverse S-box tables. These will be computed + * on the client so that we don't have to send them down the wire. + * + * There are two tables, _tables[0] is for encryption and + * _tables[1] is for decryption. + * + * The first 4 sub-tables are the expanded S-box with MixColumns. The + * last (_tables[01][4]) is the S-box itself. + * + * @private + */ + // if we have yet to precompute the S-box tables + // do so now + if (!aesTables) { + aesTables = precompute(); + } + // then make a copy of that object for use + this._tables = [[aesTables[0][0].slice(), aesTables[0][1].slice(), aesTables[0][2].slice(), aesTables[0][3].slice(), aesTables[0][4].slice()], [aesTables[1][0].slice(), aesTables[1][1].slice(), aesTables[1][2].slice(), aesTables[1][3].slice(), aesTables[1][4].slice()]]; + var i = undefined; + var j = undefined; + var tmp = undefined; + var encKey = undefined; + var decKey = undefined; + var sbox = this._tables[0][4]; + var decTable = this._tables[1]; + var keyLen = key.length; + var rcon = 1; + + if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) { + throw new Error('Invalid aes key size'); + } + + encKey = key.slice(0); + decKey = []; + this._key = [encKey, decKey]; + + // schedule encryption keys + for (i = keyLen; i < 4 * keyLen + 28; i++) { + tmp = encKey[i - 1]; + + // apply sbox + if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) { + tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; + + // shift rows and add rcon + if (i % keyLen === 0) { + tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24; + rcon = rcon << 1 ^ (rcon >> 7) * 283; + } + } + + encKey[i] = encKey[i - keyLen] ^ tmp; + } + + // schedule decryption keys + for (j = 0; i; j++, i--) { + tmp = encKey[j & 3 ? i : i - 4]; + if (i <= 4 || j < 4) { + decKey[j] = tmp; + } else { + decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]]; + } + } + } + + /** + * Decrypt 16 bytes, specified as four 32-bit words. + * + * @param {Number} encrypted0 the first word to decrypt + * @param {Number} encrypted1 the second word to decrypt + * @param {Number} encrypted2 the third word to decrypt + * @param {Number} encrypted3 the fourth word to decrypt + * @param {Int32Array} out the array to write the decrypted words + * into + * @param {Number} offset the offset into the output array to start + * writing results + * @return {Array} The plaintext. + */ + + _createClass(AES, [{ + key: 'decrypt', + value: function decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) { + var key = this._key[1]; + // state variables a,b,c,d are loaded with pre-whitened data + var a = encrypted0 ^ key[0]; + var b = encrypted3 ^ key[1]; + var c = encrypted2 ^ key[2]; + var d = encrypted1 ^ key[3]; + var a2 = undefined; + var b2 = undefined; + var c2 = undefined; + + // key.length === 2 ? + var nInnerRounds = key.length / 4 - 2; + var i = undefined; + var kIndex = 4; + var table = this._tables[1]; + + // load up the tables + var table0 = table[0]; + var table1 = table[1]; + var table2 = table[2]; + var table3 = table[3]; + var sbox = table[4]; + + // Inner rounds. Cribbed from OpenSSL. + for (i = 0; i < nInnerRounds; i++) { + a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex]; + b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1]; + c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2]; + d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3]; + kIndex += 4; + a = a2;b = b2;c = c2; + } + + // Last round. + for (i = 0; i < 4; i++) { + out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++]; + a2 = a;a = b;b = c;c = d;d = a2; + } + } + }]); + + return AES; +})(); + +exports['default'] = AES; +module.exports = exports['default']; +},{}],4:[function(require,module,exports){ +/** + * @file decrypter/async-stream.js + */ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _stream = require('../stream'); + +var _stream2 = _interopRequireDefault(_stream); + +/** + * A wrapper around the Stream class to use setTiemout + * and run stream "jobs" Asynchronously + * + * @class AsyncStream + * @extends Stream + */ + +var AsyncStream = (function (_Stream) { + _inherits(AsyncStream, _Stream); + + function AsyncStream() { + _classCallCheck(this, AsyncStream); + + _get(Object.getPrototypeOf(AsyncStream.prototype), 'constructor', this).call(this, _stream2['default']); + this.jobs = []; + this.delay = 1; + this.timeout_ = null; + } + + /** + * process an async job + * + * @private + */ + + _createClass(AsyncStream, [{ + key: 'processJob_', + value: function processJob_() { + this.jobs.shift()(); + if (this.jobs.length) { + this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); + } else { + this.timeout_ = null; + } + } + + /** + * push a job into the stream + * + * @param {Function} job the job to push into the stream + */ + }, { + key: 'push', + value: function push(job) { + this.jobs.push(job); + if (!this.timeout_) { + this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); + } + } + }]); + + return AsyncStream; +})(_stream2['default']); + +exports['default'] = AsyncStream; +module.exports = exports['default']; +},{"../stream":16}],5:[function(require,module,exports){ +/** + * @file decrypter/decrypter.js + * + * An asynchronous implementation of AES-128 CBC decryption with + * PKCS#7 padding. + */ + +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +var _aes = require('./aes'); + +var _aes2 = _interopRequireDefault(_aes); + +var _asyncStream = require('./async-stream'); + +var _asyncStream2 = _interopRequireDefault(_asyncStream); + +var _pkcs7 = require('pkcs7'); + +/** + * Convert network-order (big-endian) bytes into their little-endian + * representation. + */ +var ntoh = function ntoh(word) { + return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24; +}; + +/** + * Decrypt bytes using AES-128 with CBC and PKCS#7 padding. + * + * @param {Uint8Array} encrypted the encrypted bytes + * @param {Uint32Array} key the bytes of the decryption key + * @param {Uint32Array} initVector the initialization vector (IV) to + * use for the first round of CBC. + * @return {Uint8Array} the decrypted bytes + * + * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard + * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29 + * @see https://tools.ietf.org/html/rfc2315 + */ +var decrypt = function decrypt(encrypted, key, initVector) { + // word-level access to the encrypted bytes + var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2); + + var decipher = new _aes2['default'](Array.prototype.slice.call(key)); + + // byte and word-level access for the decrypted output + var decrypted = new Uint8Array(encrypted.byteLength); + var decrypted32 = new Int32Array(decrypted.buffer); + + // temporary variables for working with the IV, encrypted, and + // decrypted data + var init0 = undefined; + var init1 = undefined; + var init2 = undefined; + var init3 = undefined; + var encrypted0 = undefined; + var encrypted1 = undefined; + var encrypted2 = undefined; + var encrypted3 = undefined; + + // iteration variable + var wordIx = undefined; + + // pull out the words of the IV to ensure we don't modify the + // passed-in reference and easier access + init0 = initVector[0]; + init1 = initVector[1]; + init2 = initVector[2]; + init3 = initVector[3]; + + // decrypt four word sequences, applying cipher-block chaining (CBC) + // to each decrypted block + for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) { + // convert big-endian (network order) words into little-endian + // (javascript order) + encrypted0 = ntoh(encrypted32[wordIx]); + encrypted1 = ntoh(encrypted32[wordIx + 1]); + encrypted2 = ntoh(encrypted32[wordIx + 2]); + encrypted3 = ntoh(encrypted32[wordIx + 3]); + + // decrypt the block + decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); + + // XOR with the IV, and restore network byte-order to obtain the + // plaintext + decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0); + decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1); + decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2); + decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); + + // setup the IV for the next round + init0 = encrypted0; + init1 = encrypted1; + init2 = encrypted2; + init3 = encrypted3; + } + + return decrypted; +}; + +exports.decrypt = decrypt; +/** + * The `Decrypter` class that manages decryption of AES + * data through `AsyncStream` objects and the `decrypt` + * function + * + * @param {Uint8Array} encrypted the encrypted bytes + * @param {Uint32Array} key the bytes of the decryption key + * @param {Uint32Array} initVector the initialization vector (IV) to + * @param {Function} done the function to run when done + * @class Decrypter + */ + +var Decrypter = (function () { + function Decrypter(encrypted, key, initVector, done) { + _classCallCheck(this, Decrypter); + + var step = Decrypter.STEP; + var encrypted32 = new Int32Array(encrypted.buffer); + var decrypted = new Uint8Array(encrypted.byteLength); + var i = 0; + + this.asyncStream_ = new _asyncStream2['default'](); + + // split up the encryption job and do the individual chunks asynchronously + this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); + for (i = step; i < encrypted32.length; i += step) { + initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]); + this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); + } + // invoke the done() callback when everything is finished + this.asyncStream_.push(function () { + // remove pkcs#7 padding from the decrypted bytes + done(null, (0, _pkcs7.unpad)(decrypted)); + }); + } + + /** + * a getter for step the maximum number of bytes to process at one time + * + * @return {Number} the value of step 32000 + */ + + _createClass(Decrypter, [{ + key: 'decryptChunk_', + + /** + * @private + */ + value: function decryptChunk_(encrypted, key, initVector, decrypted) { + return function () { + var bytes = decrypt(encrypted, key, initVector); + + decrypted.set(bytes, encrypted.byteOffset); + }; + } + }], [{ + key: 'STEP', + get: function get() { + // 4 * 8000; + return 32000; + } + }]); + + return Decrypter; +})(); + +exports.Decrypter = Decrypter; +exports['default'] = { + Decrypter: Decrypter, + decrypt: decrypt +}; +},{"./aes":3,"./async-stream":4,"pkcs7":62}],6:[function(require,module,exports){ +/** + * @file decrypter/index.js + * + * Index module to easily import the primary components of AES-128 + * decryption. Like this: + * + * ```js + * import {Decrypter, decrypt, AsyncStream} from './src/decrypter'; + * ``` + */ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _decrypter = require('./decrypter'); + +var _asyncStream = require('./async-stream'); + +var _asyncStream2 = _interopRequireDefault(_asyncStream); + +exports['default'] = { + decrypt: _decrypter.decrypt, + Decrypter: _decrypter.Decrypter, + AsyncStream: _asyncStream2['default'] +}; +module.exports = exports['default']; +},{"./async-stream":4,"./decrypter":5}],7:[function(require,module,exports){ +(function (global){ +/** + * @file hls-audio-track.js + */ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +var _get = function get(_x2, _x3, _x4) { var _again = true; _function: while (_again) { var object = _x2, property = _x3, receiver = _x4; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x2 = parent; _x3 = property; _x4 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); + +var _playlistLoader = require('./playlist-loader'); + +var _playlistLoader2 = _interopRequireDefault(_playlistLoader); + +/** + * HlsAudioTrack extends video.js audio tracks but adds HLS + * specific data storage such as playlist loaders, mediaGroups + * and default/autoselect + * + * @param {Object} options options to create HlsAudioTrack with + * @class HlsAudioTrack + * @extends AudioTrack + */ + +var HlsAudioTrack = (function (_AudioTrack) { + _inherits(HlsAudioTrack, _AudioTrack); + + function HlsAudioTrack(options) { + _classCallCheck(this, HlsAudioTrack); + + _get(Object.getPrototypeOf(HlsAudioTrack.prototype), 'constructor', this).call(this, { + kind: options['default'] ? 'main' : 'alternative', + enabled: options['default'] || false, + language: options.language, + label: options.label + }); + + this.hls = options.hls; + this.autoselect = options.autoselect || false; + this['default'] = options['default'] || false; + this.withCredentials = options.withCredentials || false; + this.mediaGroups_ = []; + this.addLoader(options.mediaGroup, options.resolvedUri); + } + + /** + * get a PlaylistLoader from this track given a mediaGroup name + * + * @param {String} mediaGroup the mediaGroup to get the loader for + * @return {PlaylistLoader|Null} the PlaylistLoader or null + */ + + _createClass(HlsAudioTrack, [{ + key: 'getLoader', + value: function getLoader(mediaGroup) { + for (var i = 0; i < this.mediaGroups_.length; i++) { + var mgl = this.mediaGroups_[i]; + + if (mgl.mediaGroup === mediaGroup) { + return mgl.loader; + } + } + } + + /** + * add a PlaylistLoader given a mediaGroup, and a uri. for a combined track + * we store null for the playlistloader + * + * @param {String} mediaGroup the mediaGroup to get the loader for + * @param {String} uri the uri to get the audio track/mediaGroup from + */ + }, { + key: 'addLoader', + value: function addLoader(mediaGroup) { + var uri = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1]; + + var loader = null; + + if (uri) { + // TODO: this should probably happen upstream in Master Playlist + // Controller when we can switch PlaylistLoader sources + // then we can just store the uri here instead + loader = new _playlistLoader2['default'](uri, this.hls, this.withCredentials); + } + this.mediaGroups_.push({ mediaGroup: mediaGroup, loader: loader }); + } + + /** + * remove a playlist loader from a track given the mediaGroup + * + * @param {String} mediaGroup the mediaGroup to remove + */ + }, { + key: 'removeLoader', + value: function removeLoader(mediaGroup) { + for (var i = 0; i < this.mediaGroups_.length; i++) { + var mgl = this.mediaGroups_[i]; + + if (mgl.mediaGroup === mediaGroup) { + if (mgl.loader) { + mgl.loader.dispose(); + } + this.mediaGroups_.splice(i, 1); + return; + } + } + } + + /** + * Dispose of this audio track and + * the playlist loader that it holds inside + */ + }, { + key: 'dispose', + value: function dispose() { + var i = this.mediaGroups_.length; + + while (i--) { + this.removeLoader(this.mediaGroups_[i].mediaGroup); + } + } + }]); + + return HlsAudioTrack; +})(_videoJs.AudioTrack); + +exports['default'] = HlsAudioTrack; +module.exports = exports['default']; +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./playlist-loader":9}],8:[function(require,module,exports){ +(function (global){ +/** + * @file master-playlist-controller.js + */ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +var _get = function get(_x2, _x3, _x4) { var _again = true; _function: while (_again) { var object = _x2, property = _x3, receiver = _x4; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x2 = parent; _x3 = property; _x4 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _playlistLoader = require('./playlist-loader'); + +var _playlistLoader2 = _interopRequireDefault(_playlistLoader); + +var _segmentLoader = require('./segment-loader'); + +var _segmentLoader2 = _interopRequireDefault(_segmentLoader); + +var _ranges = require('./ranges'); + +var _ranges2 = _interopRequireDefault(_ranges); + +var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); + +var _videoJs2 = _interopRequireDefault(_videoJs); + +var _hlsAudioTrack = require('./hls-audio-track'); + +var _hlsAudioTrack2 = _interopRequireDefault(_hlsAudioTrack); + +// 5 minute blacklist +var BLACKLIST_DURATION = 5 * 60 * 1000; +var Hls = undefined; + +var parseCodecs = function parseCodecs(codecs) { + var result = { + codecCount: 0, + videoCodec: null, + audioProfile: null + }; + + result.codecCount = codecs.split(',').length; + result.codecCount = result.codecCount || 2; + + // parse the video codec but ignore the version + result.videoCodec = /(^|\s|,)+(avc1)[^ ,]*/i.exec(codecs); + result.videoCodec = result.videoCodec && result.videoCodec[2]; + + // parse the last field of the audio codec + result.audioProfile = /(^|\s|,)+mp4a.\d+\.(\d+)/i.exec(codecs); + result.audioProfile = result.audioProfile && result.audioProfile[2]; + + return result; +}; + +/** + * the master playlist controller controller all interactons + * between playlists and segmentloaders. At this time this mainly + * involves a master playlist and a series of audio playlists + * if they are available + * + * @class MasterPlaylistController + * @extends videojs.EventTarget + */ + +var MasterPlaylistController = (function (_videojs$EventTarget) { + _inherits(MasterPlaylistController, _videojs$EventTarget); + + function MasterPlaylistController(_ref) { + var _this = this; + + var url = _ref.url; + var withCredentials = _ref.withCredentials; + var mode = _ref.mode; + var tech = _ref.tech; + var bandwidth = _ref.bandwidth; + var externHls = _ref.externHls; + + _classCallCheck(this, MasterPlaylistController); + + _get(Object.getPrototypeOf(MasterPlaylistController.prototype), 'constructor', this).call(this); + + Hls = externHls; + + this.withCredentials = withCredentials; + this.tech_ = tech; + this.hls_ = tech.hls; + this.mode_ = mode; + this.audioTracks_ = []; + + this.mediaSource = new _videoJs2['default'].MediaSource({ mode: mode }); + this.mediaSource.on('audioinfo', function (e) { + return _this.trigger(e); + }); + // load the media source into the player + this.mediaSource.addEventListener('sourceopen', this.handleSourceOpen_.bind(this)); + + var segmentLoaderOptions = { + hls: this.hls_, + mediaSource: this.mediaSource, + currentTime: this.tech_.currentTime.bind(this.tech_), + withCredentials: this.withCredentials, + seekable: function seekable() { + return _this.seekable(); + }, + seeking: function seeking() { + return _this.tech_.seeking(); + }, + setCurrentTime: function setCurrentTime(a) { + return _this.tech_.setCurrentTime(a); + }, + hasPlayed: function hasPlayed() { + return _this.tech_.played().length !== 0; + }, + bandwidth: bandwidth + }; + + // combined audio/video or just video when alternate audio track is selected + this.mainSegmentLoader_ = new _segmentLoader2['default'](segmentLoaderOptions); + // alternate audio track + this.audioSegmentLoader_ = new _segmentLoader2['default'](segmentLoaderOptions); + + if (!url) { + throw new Error('A non-empty playlist URL is required'); + } + + this.masterPlaylistLoader_ = new _playlistLoader2['default'](url, this.hls_, this.withCredentials); + + this.masterPlaylistLoader_.on('loadedmetadata', function () { + var media = _this.masterPlaylistLoader_.media(); + + // if this isn't a live video and preload permits, start + // downloading segments + if (media.endList && _this.tech_.preload() !== 'none') { + _this.mainSegmentLoader_.playlist(media); + _this.mainSegmentLoader_.expired(_this.masterPlaylistLoader_.expired_); + _this.mainSegmentLoader_.load(); + } + + _this.setupSourceBuffer_(); + _this.setupFirstPlay(); + _this.useAudio(); + }); + + this.masterPlaylistLoader_.on('loadedplaylist', function () { + var updatedPlaylist = _this.masterPlaylistLoader_.media(); + var seekable = undefined; + + if (!updatedPlaylist) { + // select the initial variant + _this.initialMedia_ = _this.selectPlaylist(); + _this.masterPlaylistLoader_.media(_this.initialMedia_); + _this.fillAudioTracks_(); + + _this.trigger('selectedinitialmedia'); + return; + } + + // TODO: Create a new event on the PlaylistLoader that signals + // that the segments have changed in some way and use that to + // update the SegmentLoader instead of doing it twice here and + // on `mediachange` + _this.mainSegmentLoader_.playlist(updatedPlaylist); + _this.mainSegmentLoader_.expired(_this.masterPlaylistLoader_.expired_); + _this.updateDuration(); + + // update seekable + seekable = _this.seekable(); + if (!updatedPlaylist.endList && seekable.length !== 0) { + _this.mediaSource.addSeekableRange_(seekable.start(0), seekable.end(0)); + } + }); + + this.masterPlaylistLoader_.on('error', function () { + _this.blacklistCurrentPlaylist(_this.masterPlaylistLoader_.error); + }); + + this.masterPlaylistLoader_.on('mediachanging', function () { + _this.mainSegmentLoader_.pause(); + }); + + this.masterPlaylistLoader_.on('mediachange', function () { + var media = _this.masterPlaylistLoader_.media(); + + _this.mainSegmentLoader_.abort(); + + // TODO: Create a new event on the PlaylistLoader that signals + // that the segments have changed in some way and use that to + // update the SegmentLoader instead of doing it twice here and + // on `loadedplaylist` + _this.mainSegmentLoader_.playlist(media); + _this.mainSegmentLoader_.expired(_this.masterPlaylistLoader_.expired_); + _this.mainSegmentLoader_.load(); + + _this.tech_.trigger({ + type: 'mediachange', + bubbles: true + }); + }); + + this.mainSegmentLoader_.on('progress', function () { + // figure out what stream the next segment should be downloaded from + // with the updated bandwidth information + _this.masterPlaylistLoader_.media(_this.selectPlaylist()); + + _this.trigger('progress'); + }); + + this.mainSegmentLoader_.on('error', function () { + _this.blacklistCurrentPlaylist(_this.mainSegmentLoader_.error()); + }); + + this.audioSegmentLoader_.on('error', function () { + _videoJs2['default'].log.warn('Problem encountered with the current alternate audio track' + '. Switching back to default.'); + _this.audioSegmentLoader_.abort(); + _this.audioPlaylistLoader_ = null; + _this.useAudio(); + }); + + this.masterPlaylistLoader_.load(); + } + + /** + * get the total number of media requests from the `audiosegmentloader_` + * and the `mainSegmentLoader_` + * + * @private + */ + + _createClass(MasterPlaylistController, [{ + key: 'mediaRequests_', + value: function mediaRequests_() { + return this.audioSegmentLoader_.mediaRequests + this.mainSegmentLoader_.mediaRequests; + } + + /** + * get the total time that media requests have spent trnasfering + * from the `audiosegmentloader_` and the `mainSegmentLoader_` + * + * @private + */ + }, { + key: 'mediaTransferDuration_', + value: function mediaTransferDuration_() { + return this.audioSegmentLoader_.mediaTransferDuration + this.mainSegmentLoader_.mediaTransferDuration; + } + + /** + * get the total number of bytes transfered during media requests + * from the `audiosegmentloader_` and the `mainSegmentLoader_` + * + * @private + */ + }, { + key: 'mediaBytesTransferred_', + value: function mediaBytesTransferred_() { + return this.audioSegmentLoader_.mediaBytesTransferred + this.mainSegmentLoader_.mediaBytesTransferred; + } + + /** + * fill our internal list of HlsAudioTracks with data from + * the master playlist or use a default + * + * @private + */ + }, { + key: 'fillAudioTracks_', + value: function fillAudioTracks_() { + var master = this.master(); + var mediaGroups = master.mediaGroups || {}; + + // force a default if we have none or we are not + // in html5 mode (the only mode to support more than one + // audio track) + if (!mediaGroups || !mediaGroups.AUDIO || Object.keys(mediaGroups.AUDIO).length === 0 || this.mode_ !== 'html5') { + // "main" audio group, track name "default" + mediaGroups.AUDIO = { main: { 'default': { 'default': true } } }; + } + + var tracks = {}; + + for (var mediaGroup in mediaGroups.AUDIO) { + for (var label in mediaGroups.AUDIO[mediaGroup]) { + var properties = mediaGroups.AUDIO[mediaGroup][label]; + + // if the track already exists add a new "location" + // since tracks in different mediaGroups are actually the same + // track with different locations to download them from + if (tracks[label]) { + tracks[label].addLoader(mediaGroup, properties.resolvedUri); + continue; + } + + var track = new _hlsAudioTrack2['default'](_videoJs2['default'].mergeOptions(properties, { + hls: this.hls_, + withCredentials: this.withCredential, + mediaGroup: mediaGroup, + label: label + })); + + tracks[label] = track; + this.audioTracks_.push(track); + } + } + } + + /** + * Call load on our SegmentLoaders + */ + }, { + key: 'load', + value: function load() { + this.mainSegmentLoader_.load(); + if (this.audioPlaylistLoader_) { + this.audioSegmentLoader_.load(); + } + } + + /** + * Get the current active Media Group for Audio + * given the selected playlist and its attributes + */ + }, { + key: 'activeAudioGroup', + value: function activeAudioGroup() { + var media = this.masterPlaylistLoader_.media(); + var mediaGroup = 'main'; + + if (media && media.attributes && media.attributes.AUDIO) { + mediaGroup = media.attributes.AUDIO; + } + + return mediaGroup; + } + + /** + * Use any audio track that we have, and start to load it + */ + }, { + key: 'useAudio', + value: function useAudio() { + var _this2 = this; + + var track = undefined; + + this.audioTracks_.forEach(function (t) { + if (!track && t.enabled) { + track = t; + } + }); + + // called too early or no track is enabled + if (!track) { + return; + } + + // Pause any alternative audio + if (this.audioPlaylistLoader_) { + this.audioPlaylistLoader_.pause(); + this.audioPlaylistLoader_ = null; + this.audioSegmentLoader_.pause(); + } + + // If the audio track for the active audio group has + // a playlist loader than it is an alterative audio track + // otherwise it is a part of the mainSegmenLoader + var loader = track.getLoader(this.activeAudioGroup()); + + if (!loader) { + this.mainSegmentLoader_.clearBuffer(); + return; + } + + // TODO: it may be better to create the playlist loader here + // when we can change an audioPlaylistLoaders src + this.audioPlaylistLoader_ = loader; + + if (this.audioPlaylistLoader_.started) { + this.audioPlaylistLoader_.load(); + this.audioSegmentLoader_.load(); + this.audioSegmentLoader_.clearBuffer(); + return; + } + + this.audioPlaylistLoader_.on('loadedmetadata', function () { + /* eslint-disable no-shadow */ + var media = _this2.audioPlaylistLoader_.media(); + /* eslint-enable no-shadow */ + + _this2.audioSegmentLoader_.playlist(media); + _this2.addMimeType_(_this2.audioSegmentLoader_, 'mp4a.40.2', media); + + // if the video is already playing, or if this isn't a live video and preload + // permits, start downloading segments + if (!_this2.tech_.paused() || media.endList && _this2.tech_.preload() !== 'none') { + _this2.audioSegmentLoader_.load(); + } + + if (!media.endList) { + // trigger the playlist loader to start "expired time"-tracking + _this2.audioPlaylistLoader_.trigger('firstplay'); + } + }); + + this.audioPlaylistLoader_.on('loadedplaylist', function () { + var updatedPlaylist = undefined; + + if (_this2.audioPlaylistLoader_) { + updatedPlaylist = _this2.audioPlaylistLoader_.media(); + } + + if (!updatedPlaylist) { + // only one playlist to select + _this2.audioPlaylistLoader_.media(_this2.audioPlaylistLoader_.playlists.master.playlists[0]); + return; + } + + _this2.audioSegmentLoader_.playlist(updatedPlaylist); + }); + + this.audioPlaylistLoader_.on('error', function () { + _videoJs2['default'].log.warn('Problem encountered loading the alternate audio track' + '. Switching back to default.'); + _this2.audioSegmentLoader_.abort(); + _this2.audioPlaylistLoader_ = null; + _this2.useAudio(); + }); + + this.audioSegmentLoader_.clearBuffer(); + this.audioPlaylistLoader_.start(); + } + + /** + * Re-tune playback quality level for the current player + * conditions. This method may perform destructive actions, like + * removing already buffered content, to readjust the currently + * active playlist quickly. + * + * @private + */ + }, { + key: 'fastQualityChange_', + value: function fastQualityChange_() { + var media = this.selectPlaylist(); + + if (media !== this.masterPlaylistLoader_.media()) { + this.masterPlaylistLoader_.media(media); + this.mainSegmentLoader_.sourceUpdater_.remove(this.tech_.currentTime() + 5, Infinity); + } + } + + /** + * Begin playback. + */ + }, { + key: 'play', + value: function play() { + if (this.setupFirstPlay()) { + return; + } + + if (this.tech_.ended()) { + this.tech_.setCurrentTime(0); + } + + this.load(); + + // if the viewer has paused and we fell out of the live window, + // seek forward to the earliest available position + if (this.tech_.duration() === Infinity) { + if (this.tech_.currentTime() < this.tech_.seekable().start(0)) { + return this.tech_.setCurrentTime(this.tech_.seekable().start(0)); + } + } + } + + /** + * Seek to the latest media position if this is a live video and the + * player and video are loaded and initialized. + */ + }, { + key: 'setupFirstPlay', + value: function setupFirstPlay() { + var seekable = undefined; + var media = this.masterPlaylistLoader_.media(); + + // check that everything is ready to begin buffering + // 1) the active media playlist is available + if (media && + // 2) the video is a live stream + !media.endList && + + // 3) the player is not paused + !this.tech_.paused() && + + // 4) the player has not started playing + !this.hasPlayed_) { + + this.load(); + + // trigger the playlist loader to start "expired time"-tracking + this.masterPlaylistLoader_.trigger('firstplay'); + this.hasPlayed_ = true; + + // seek to the latest media position for live videos + seekable = this.seekable(); + if (seekable.length) { + this.tech_.setCurrentTime(seekable.end(0)); + } + + return true; + } + return false; + } + + /** + * handle the sourceopen event on the MediaSource + * + * @private + */ + }, { + key: 'handleSourceOpen_', + value: function handleSourceOpen_() { + // Only attempt to create the source buffer if none already exist. + // handleSourceOpen is also called when we are "re-opening" a source buffer + // after `endOfStream` has been called (in response to a seek for instance) + this.setupSourceBuffer_(); + + // if autoplay is enabled, begin playback. This is duplicative of + // code in video.js but is required because play() must be invoked + // *after* the media source has opened. + if (this.tech_.autoplay()) { + this.tech_.play(); + } + + this.trigger('sourceopen'); + } + + /** + * Blacklists a playlist when an error occurs for a set amount of time + * making it unavailable for selection by the rendition selection algorithm + * and then forces a new playlist (rendition) selection. + * + * @param {Object=} error an optional error that may include the playlist + * to blacklist + */ + }, { + key: 'blacklistCurrentPlaylist', + value: function blacklistCurrentPlaylist() { + var error = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + var currentPlaylist = undefined; + var nextPlaylist = undefined; + + // If the `error` was generated by the playlist loader, it will contain + // the playlist we were trying to load (but failed) and that should be + // blacklisted instead of the currently selected playlist which is likely + // out-of-date in this scenario + currentPlaylist = error.playlist || this.masterPlaylistLoader_.media(); + + // If there is no current playlist, then an error occurred while we were + // trying to load the master OR while we were disposing of the tech + if (!currentPlaylist) { + this.error = error; + return this.mediaSource.endOfStream('network'); + } + + // Blacklist this playlist + currentPlaylist.excludeUntil = Date.now() + BLACKLIST_DURATION; + + // Select a new playlist + nextPlaylist = this.selectPlaylist(); + + if (nextPlaylist) { + _videoJs2['default'].log.warn('Problem encountered with the current ' + 'HLS playlist. Switching to another playlist.'); + + return this.masterPlaylistLoader_.media(nextPlaylist); + } + _videoJs2['default'].log.warn('Problem encountered with the current ' + 'HLS playlist. No suitable alternatives found.'); + // We have no more playlists we can select so we must fail + this.error = error; + return this.mediaSource.endOfStream('network'); + } + + /** + * Pause all segment loaders + */ + }, { + key: 'pauseLoading', + value: function pauseLoading() { + this.mainSegmentLoader_.pause(); + if (this.audioPlaylistLoader_) { + this.audioSegmentLoader_.pause(); + } + } + + /** + * set the current time on all segment loaders + * + * @param {TimeRange} currentTime the current time to set + * @return {TimeRange} the current time + */ + }, { + key: 'setCurrentTime', + value: function setCurrentTime(currentTime) { + var buffered = _ranges2['default'].findRange(this.tech_.buffered(), currentTime); + + if (!(this.masterPlaylistLoader_ && this.masterPlaylistLoader_.media())) { + // return immediately if the metadata is not ready yet + return 0; + } + + // it's clearly an edge-case but don't thrown an error if asked to + // seek within an empty playlist + if (!this.masterPlaylistLoader_.media().segments) { + return 0; + } + + // if the seek location is already buffered, continue buffering as + // usual + if (buffered && buffered.length) { + return currentTime; + } + + // cancel outstanding requests so we begin buffering at the new + // location + this.mainSegmentLoader_.abort(); + if (this.audioPlaylistLoader_) { + this.audioSegmentLoader_.abort(); + } + + if (!this.tech_.paused()) { + this.mainSegmentLoader_.load(); + if (this.audioPlaylistLoader_) { + this.audioSegmentLoader_.load(); + } + } + } + + /** + * get the current duration + * + * @return {TimeRange} the duration + */ + }, { + key: 'duration', + value: function duration() { + if (!this.masterPlaylistLoader_) { + return 0; + } + + if (this.mediaSource) { + return this.mediaSource.duration; + } + + return Hls.Playlist.duration(this.masterPlaylistLoader_.media()); + } + + /** + * check the seekable range + * + * @return {TimeRange} the seekable range + */ + }, { + key: 'seekable', + value: function seekable() { + var media = undefined; + var mainSeekable = undefined; + var audioSeekable = undefined; + + if (!this.masterPlaylistLoader_) { + return _videoJs2['default'].createTimeRanges(); + } + media = this.masterPlaylistLoader_.media(); + if (!media) { + return _videoJs2['default'].createTimeRanges(); + } + + mainSeekable = Hls.Playlist.seekable(media, this.masterPlaylistLoader_.expired_); + if (mainSeekable.length === 0) { + return mainSeekable; + } + + if (this.audioPlaylistLoader_) { + audioSeekable = Hls.Playlist.seekable(this.audioPlaylistLoader_.media(), this.audioPlaylistLoader_.expired_); + if (audioSeekable.length === 0) { + return audioSeekable; + } + } + + if (!audioSeekable) { + // seekable has been calculated based on buffering video data so it + // can be returned directly + return mainSeekable; + } + + return _videoJs2['default'].createTimeRanges([[audioSeekable.start(0) > mainSeekable.start(0) ? audioSeekable.start(0) : mainSeekable.start(0), audioSeekable.end(0) < mainSeekable.end(0) ? audioSeekable.end(0) : mainSeekable.end(0)]]); + } + + /** + * Update the player duration + */ + }, { + key: 'updateDuration', + value: function updateDuration() { + var _this3 = this; + + var oldDuration = this.mediaSource.duration; + var newDuration = Hls.Playlist.duration(this.masterPlaylistLoader_.media()); + var buffered = this.tech_.buffered(); + var setDuration = function setDuration() { + _this3.mediaSource.duration = newDuration; + _this3.tech_.trigger('durationchange'); + + _this3.mediaSource.removeEventListener('sourceopen', setDuration); + }; + + if (buffered.length > 0) { + newDuration = Math.max(newDuration, buffered.end(buffered.length - 1)); + } + + // if the duration has changed, invalidate the cached value + if (oldDuration !== newDuration) { + // update the duration + if (this.mediaSource.readyState !== 'open') { + this.mediaSource.addEventListener('sourceopen', setDuration); + } else { + setDuration(); + } + } + } + + /** + * dispose of the MasterPlaylistController and everything + * that it controls + */ + }, { + key: 'dispose', + value: function dispose() { + this.masterPlaylistLoader_.dispose(); + this.audioTracks_.forEach(function (track) { + track.dispose(); + }); + this.audioTracks_.length = 0; + this.mainSegmentLoader_.dispose(); + this.audioSegmentLoader_.dispose(); + } + + /** + * return the master playlist object if we have one + * + * @return {Object} the master playlist object that we parsed + */ + }, { + key: 'master', + value: function master() { + return this.masterPlaylistLoader_.master; + } + + /** + * return the currently selected playlist + * + * @return {Object} the currently selected playlist object that we parsed + */ + }, { + key: 'media', + value: function media() { + // playlist loader will not return media if it has not been fully loaded + return this.masterPlaylistLoader_.media() || this.initialMedia_; + } + + /** + * setup our internal source buffers on our segment Loaders + * + * @private + */ + }, { + key: 'setupSourceBuffer_', + value: function setupSourceBuffer_() { + var media = this.masterPlaylistLoader_.media(); + + // wait until a media playlist is available and the Media Source is + // attached + if (!media || this.mediaSource.readyState !== 'open') { + return; + } + + this.addMimeType_(this.mainSegmentLoader_, 'avc1.4d400d, mp4a.40.2', media); + + // exclude any incompatible variant streams from future playlist + // selection + this.excludeIncompatibleVariants_(media); + } + + /** + * add a time type to a segmentLoader + * + * @param {SegmentLoader} segmentLoader the segmentloader to work on + * @param {String} codecs to use by default + * @param {Object} the parsed media object + * @private + */ + }, { + key: 'addMimeType_', + value: function addMimeType_(segmentLoader, defaultCodecs, media) { + var mimeType = 'video/mp2t'; + + // if the codecs were explicitly specified, pass them along to the + // source buffer + if (media.attributes && media.attributes.CODECS) { + mimeType += '; codecs="' + media.attributes.CODECS + '"'; + } else { + mimeType += '; codecs="' + defaultCodecs + '"'; + } + segmentLoader.mimeType(mimeType); + } + + /** + * Blacklist playlists that are known to be codec or + * stream-incompatible with the SourceBuffer configuration. For + * instance, Media Source Extensions would cause the video element to + * stall waiting for video data if you switched from a variant with + * video and audio to an audio-only one. + * + * @param {Object} media a media playlist compatible with the current + * set of SourceBuffers. Variants in the current master playlist that + * do not appear to have compatible codec or stream configurations + * will be excluded from the default playlist selection algorithm + * indefinitely. + * @private + */ + }, { + key: 'excludeIncompatibleVariants_', + value: function excludeIncompatibleVariants_(media) { + var master = this.masterPlaylistLoader_.master; + var codecCount = 2; + var videoCodec = null; + var audioProfile = null; + var codecs = undefined; + + if (media.attributes && media.attributes.CODECS) { + codecs = parseCodecs(media.attributes.CODECS); + videoCodec = codecs.videoCodec; + audioProfile = codecs.audioProfile; + codecCount = codecs.codecCount; + } + master.playlists.forEach(function (variant) { + var variantCodecs = { + codecCount: 2, + videoCodec: null, + audioProfile: null + }; + + if (variant.attributes && variant.attributes.CODECS) { + variantCodecs = parseCodecs(variant.attributes.CODECS); + } + + // if the streams differ in the presence or absence of audio or + // video, they are incompatible + if (variantCodecs.codecCount !== codecCount) { + variant.excludeUntil = Infinity; + } + + // if h.264 is specified on the current playlist, some flavor of + // it must be specified on all compatible variants + if (variantCodecs.videoCodec !== videoCodec) { + variant.excludeUntil = Infinity; + } + // HE-AAC ("mp4a.40.5") is incompatible with all other versions of + // AAC audio in Chrome 46. Don't mix the two. + if (variantCodecs.audioProfile === '5' && audioProfile !== '5' || audioProfile === '5' && variantCodecs.audioProfile !== '5') { + variant.excludeUntil = Infinity; + } + }); + } + }]); + + return MasterPlaylistController; +})(_videoJs2['default'].EventTarget); + +exports['default'] = MasterPlaylistController; +module.exports = exports['default']; +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./hls-audio-track":7,"./playlist-loader":9,"./ranges":11,"./segment-loader":14}],9:[function(require,module,exports){ +(function (global){ +/** + * @file playlist-loader.js + * + * A state machine that manages the loading, caching, and updating of + * M3U8 playlists. + * + */ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _resolveUrl = require('./resolve-url'); + +var _resolveUrl2 = _interopRequireDefault(_resolveUrl); + +var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); + +var _stream = require('./stream'); + +var _stream2 = _interopRequireDefault(_stream); + +var _m3u8Parser = require('m3u8-parser'); + +var _m3u8Parser2 = _interopRequireDefault(_m3u8Parser); + +/** + * Returns a new array of segments that is the result of merging + * properties from an older list of segments onto an updated + * list. No properties on the updated playlist will be overridden. + * + * @param {Array} original the outdated list of segments + * @param {Array} update the updated list of segments + * @param {Number=} offset the index of the first update + * segment in the original segment list. For non-live playlists, + * this should always be zero and does not need to be + * specified. For live playlists, it should be the difference + * between the media sequence numbers in the original and updated + * playlists. + * @return a list of merged segment objects + */ +var updateSegments = function updateSegments(original, update, offset) { + var result = update.slice(); + var length = undefined; + var i = undefined; + + offset = offset || 0; + length = Math.min(original.length, update.length + offset); + + for (i = offset; i < length; i++) { + result[i - offset] = (0, _videoJs.mergeOptions)(original[i], result[i - offset]); + } + return result; +}; + +/** + * Returns a new master playlist that is the result of merging an + * updated media playlist into the original version. If the + * updated media playlist does not match any of the playlist + * entries in the original master playlist, null is returned. + * + * @param {Object} master a parsed master M3U8 object + * @param {Object} media a parsed media M3U8 object + * @return {Object} a new object that represents the original + * master playlist with the updated media playlist merged in, or + * null if the merge produced no change. + */ +var updateMaster = function updateMaster(master, media) { + var changed = false; + var result = (0, _videoJs.mergeOptions)(master, {}); + var i = master.playlists.length; + var playlist = undefined; + var segment = undefined; + var j = undefined; + + while (i--) { + playlist = result.playlists[i]; + if (playlist.uri === media.uri) { + // consider the playlist unchanged if the number of segments + // are equal and the media sequence number is unchanged + if (playlist.segments && media.segments && playlist.segments.length === media.segments.length && playlist.mediaSequence === media.mediaSequence) { + continue; + } + + result.playlists[i] = (0, _videoJs.mergeOptions)(playlist, media); + result.playlists[media.uri] = result.playlists[i]; + + // if the update could overlap existing segment information, + // merge the two lists + if (playlist.segments) { + result.playlists[i].segments = updateSegments(playlist.segments, media.segments, media.mediaSequence - playlist.mediaSequence); + } + // resolve any missing segment and key URIs + j = 0; + if (result.playlists[i].segments) { + j = result.playlists[i].segments.length; + } + while (j--) { + segment = result.playlists[i].segments[j]; + if (!segment.resolvedUri) { + segment.resolvedUri = (0, _resolveUrl2['default'])(playlist.resolvedUri, segment.uri); + } + if (segment.key && !segment.key.resolvedUri) { + segment.key.resolvedUri = (0, _resolveUrl2['default'])(playlist.resolvedUri, segment.key.uri); + } + } + changed = true; + } + } + return changed ? result : null; +}; + +/** + * Load a playlist from a remote loacation + * + * @class PlaylistLoader + * @extends Stream + * @param {String} srcUrl the url to start with + * @param {Boolean} withCredentials the withCredentials xhr option + * @constructor + */ +var PlaylistLoader = function PlaylistLoader(srcUrl, hls, withCredentials) { + var _this = this; + + /* eslint-disable consistent-this */ + var loader = this; + /* eslint-enable consistent-this */ + var dispose = undefined; + var mediaUpdateTimeout = undefined; + var request = undefined; + var playlistRequestError = undefined; + var haveMetadata = undefined; + + PlaylistLoader.prototype.constructor.call(this); + + this.hls_ = hls; + + // a flag that disables "expired time"-tracking this setting has + // no effect when not playing a live stream + this.trackExpiredTime_ = false; + + if (!srcUrl) { + throw new Error('A non-empty playlist URL is required'); + } + + playlistRequestError = function (xhr, url, startingState) { + loader.setBandwidth(request || xhr); + + // any in-flight request is now finished + request = null; + + if (startingState) { + loader.state = startingState; + } + + loader.error = { + playlist: loader.master.playlists[url], + status: xhr.status, + message: 'HLS playlist request error at URL: ' + url, + responseText: xhr.responseText, + code: xhr.status >= 500 ? 4 : 2 + }; + + loader.trigger('error'); + }; + + // update the playlist loader's state in response to a new or + // updated playlist. + haveMetadata = function (xhr, url) { + var parser = undefined; + var refreshDelay = undefined; + var update = undefined; + + loader.setBandwidth(request || xhr); + + // any in-flight request is now finished + request = null; + + loader.state = 'HAVE_METADATA'; + + parser = new _m3u8Parser2['default'].Parser(); + parser.push(xhr.responseText); + parser.end(); + parser.manifest.uri = url; + + // merge this playlist into the master + update = updateMaster(loader.master, parser.manifest); + refreshDelay = (parser.manifest.targetDuration || 10) * 1000; + if (update) { + loader.master = update; + loader.updateMediaPlaylist_(parser.manifest); + } else { + // if the playlist is unchanged since the last reload, + // try again after half the target duration + refreshDelay /= 2; + } + + // refresh live playlists after a target duration passes + if (!loader.media().endList) { + window.clearTimeout(mediaUpdateTimeout); + mediaUpdateTimeout = window.setTimeout(function () { + loader.trigger('mediaupdatetimeout'); + }, refreshDelay); + } + + loader.trigger('loadedplaylist'); + }; + + // initialize the loader state + loader.state = 'HAVE_NOTHING'; + + // track the time that has expired from the live window + // this allows the seekable start range to be calculated even if + // all segments with timing information have expired + this.expired_ = 0; + + // capture the prototype dispose function + dispose = this.dispose; + + /** + * Abort any outstanding work and clean up. + */ + loader.dispose = function () { + loader.stopRequest(); + window.clearTimeout(mediaUpdateTimeout); + dispose.call(this); + }; + + loader.stopRequest = function () { + if (request) { + var oldRequest = request; + + request = null; + oldRequest.onreadystatechange = null; + oldRequest.abort(); + } + }; + + /** + * When called without any arguments, returns the currently + * active media playlist. When called with a single argument, + * triggers the playlist loader to asynchronously switch to the + * specified media playlist. Calling this method while the + * loader is in the HAVE_NOTHING causes an error to be emitted + * but otherwise has no effect. + * + * @param {Object=} playlis tthe parsed media playlist + * object to switch to + * @return {Playlist} the current loaded media + */ + loader.media = function (playlist) { + var startingState = loader.state; + var mediaChange = undefined; + + // getter + if (!playlist) { + return loader.media_; + } + + // setter + if (loader.state === 'HAVE_NOTHING') { + throw new Error('Cannot switch media playlist from ' + loader.state); + } + + // find the playlist object if the target playlist has been + // specified by URI + if (typeof playlist === 'string') { + if (!loader.master.playlists[playlist]) { + throw new Error('Unknown playlist URI: ' + playlist); + } + playlist = loader.master.playlists[playlist]; + } + + mediaChange = !loader.media_ || playlist.uri !== loader.media_.uri; + + // switch to fully loaded playlists immediately + if (loader.master.playlists[playlist.uri].endList) { + // abort outstanding playlist requests + if (request) { + request.onreadystatechange = null; + request.abort(); + request = null; + } + loader.state = 'HAVE_METADATA'; + loader.media_ = playlist; + + // trigger media change if the active media has been updated + if (mediaChange) { + loader.trigger('mediachanging'); + loader.trigger('mediachange'); + } + return; + } + + // switching to the active playlist is a no-op + if (!mediaChange) { + return; + } + + loader.state = 'SWITCHING_MEDIA'; + + // there is already an outstanding playlist request + if (request) { + if ((0, _resolveUrl2['default'])(loader.master.uri, playlist.uri) === request.url) { + // requesting to switch to the same playlist multiple times + // has no effect after the first + return; + } + request.onreadystatechange = null; + request.abort(); + request = null; + } + + // request the new playlist + if (this.media_) { + this.trigger('mediachanging'); + } + request = this.hls_.xhr({ + uri: (0, _resolveUrl2['default'])(loader.master.uri, playlist.uri), + withCredentials: withCredentials + }, function (error, req) { + // disposed + if (!request) { + return; + } + + if (error) { + return playlistRequestError(request, playlist.uri, startingState); + } + + haveMetadata(req, playlist.uri); + + // fire loadedmetadata the first time a media playlist is loaded + if (startingState === 'HAVE_MASTER') { + loader.trigger('loadedmetadata'); + } else { + loader.trigger('mediachange'); + } + }); + }; + + /** + * set the bandwidth on an xhr to the bandwidth on the playlist + */ + loader.setBandwidth = function (xhr) { + loader.bandwidth = xhr.bandwidth; + }; + + // In a live playlist, don't keep track of the expired time + // until HLS tells us that "first play" has commenced + loader.on('firstplay', function () { + this.trackExpiredTime_ = true; + }); + + // live playlist staleness timeout + loader.on('mediaupdatetimeout', function () { + if (loader.state !== 'HAVE_METADATA') { + // only refresh the media playlist if no other activity is going on + return; + } + + loader.state = 'HAVE_CURRENT_METADATA'; + request = this.hls_.xhr({ + uri: (0, _resolveUrl2['default'])(loader.master.uri, loader.media().uri), + withCredentials: withCredentials + }, function (error, req) { + // disposed + if (!request) { + return; + } + + if (error) { + return playlistRequestError(request, loader.media().uri); + } + haveMetadata(request, loader.media().uri); + }); + }); + + /** + * pause loading of the playlist + */ + loader.pause = function () { + loader.stopRequest(); + window.clearTimeout(mediaUpdateTimeout); + }; + + /** + * start loading of the playlist + */ + loader.load = function () { + if (loader.started) { + if (!loader.media().endList) { + loader.trigger('mediaupdatetimeout'); + } else { + loader.trigger('loadedplaylist'); + } + } else { + loader.start(); + } + }; + + /** + * start loading of the playlist + */ + loader.start = function () { + loader.started = true; + + // request the specified URL + request = _this.hls_.xhr({ + uri: srcUrl, + withCredentials: withCredentials + }, function (error, req) { + var parser = undefined; + var playlist = undefined; + var i = undefined; + + // disposed + if (!request) { + return; + } + + // clear the loader's request reference + request = null; + + if (error) { + loader.error = { + status: req.status, + message: 'HLS playlist request error at URL: ' + srcUrl, + responseText: req.responseText, + // MEDIA_ERR_NETWORK + code: 2 + }; + return loader.trigger('error'); + } + + parser = new _m3u8Parser2['default'].Parser(); + parser.push(req.responseText); + parser.end(); + + loader.state = 'HAVE_MASTER'; + + parser.manifest.uri = srcUrl; + + // loaded a master playlist + if (parser.manifest.playlists) { + loader.master = parser.manifest; + + // setup by-URI lookups and resolve media playlist URIs + i = loader.master.playlists.length; + while (i--) { + playlist = loader.master.playlists[i]; + loader.master.playlists[playlist.uri] = playlist; + playlist.resolvedUri = (0, _resolveUrl2['default'])(loader.master.uri, playlist.uri); + } + + // resolve any media group URIs + for (var groupKey in loader.master.mediaGroups.AUDIO) { + for (var labelKey in loader.master.mediaGroups.AUDIO[groupKey]) { + var alternateAudio = loader.master.mediaGroups.AUDIO[groupKey][labelKey]; + + if (alternateAudio.uri) { + alternateAudio.resolvedUri = (0, _resolveUrl2['default'])(loader.master.uri, alternateAudio.uri); + } + } + } + + loader.trigger('loadedplaylist'); + if (!request) { + // no media playlist was specifically selected so start + // from the first listed one + loader.media(parser.manifest.playlists[0]); + } + return; + } + + // loaded a media playlist + // infer a master playlist if none was previously requested + loader.master = { + uri: window.location.href, + playlists: [{ + uri: srcUrl + }] + }; + loader.master.playlists[srcUrl] = loader.master.playlists[0]; + loader.master.playlists[0].resolvedUri = srcUrl; + haveMetadata(req, srcUrl); + return loader.trigger('loadedmetadata'); + }); + }; +}; + +PlaylistLoader.prototype = new _stream2['default'](); + +/** + * Update the PlaylistLoader state to reflect the changes in an + * update to the current media playlist. + * + * @param {Object} update the updated media playlist object + */ +PlaylistLoader.prototype.updateMediaPlaylist_ = function (update) { + var outdated = undefined; + var i = undefined; + var segment = undefined; + + outdated = this.media_; + this.media_ = this.master.playlists[update.uri]; + + if (!outdated) { + return; + } + + // don't track expired time until this flag is truthy + if (!this.trackExpiredTime_) { + return; + } + + // if the update was the result of a rendition switch do not + // attempt to calculate expired_ since media-sequences need not + // correlate between renditions/variants + if (update.uri !== outdated.uri) { + return; + } + + // try using precise timing from first segment of the updated + // playlist + if (update.segments.length) { + if (typeof update.segments[0].start !== 'undefined') { + this.expired_ = update.segments[0].start; + return; + } else if (typeof update.segments[0].end !== 'undefined') { + this.expired_ = update.segments[0].end - update.segments[0].duration; + return; + } + } + + // calculate expired by walking the outdated playlist + i = update.mediaSequence - outdated.mediaSequence - 1; + + for (; i >= 0; i--) { + segment = outdated.segments[i]; + + if (!segment) { + // we missed information on this segment completely between + // playlist updates so we'll have to take an educated guess + // once we begin buffering again, any error we introduce can + // be corrected + this.expired_ += outdated.targetDuration || 10; + continue; + } + + if (typeof segment.end !== 'undefined') { + this.expired_ = segment.end; + return; + } + if (typeof segment.start !== 'undefined') { + this.expired_ = segment.start + segment.duration; + return; + } + this.expired_ += segment.duration; + } +}; + +exports['default'] = PlaylistLoader; +module.exports = exports['default']; +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./resolve-url":13,"./stream":16,"m3u8-parser":56}],10:[function(require,module,exports){ +(function (global){ +/** + * @file playlist.js + * + * Playlist related utilities. + */ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); + +var Playlist = { + /** + * The number of segments that are unsafe to start playback at in + * a live stream. Changing this value can cause playback stalls. + * See HTTP Live Streaming, "Playing the Media Playlist File" + * https://tools.ietf.org/html/draft-pantos-http-live-streaming-18#section-6.3.3 + */ + UNSAFE_LIVE_SEGMENTS: 3 +}; + +/** + * walk backward until we find a duration we can use + * or return a failure + * + * @param {Playlist} playlist the playlist to walk through + * @param {Number} endSequence the mediaSequence to stop walking on + */ + +var backwardDuration = function backwardDuration(playlist, endSequence) { + var result = 0; + var i = endSequence - playlist.mediaSequence; + // if a start time is available for segment immediately following + // the interval, use it + var segment = playlist.segments[i]; + + // Walk backward until we find the latest segment with timeline + // information that is earlier than endSequence + if (segment) { + if (typeof segment.start !== 'undefined') { + return { result: segment.start, precise: true }; + } + if (typeof segment.end !== 'undefined') { + return { + result: segment.end - segment.duration, + precise: true + }; + } + } + while (i--) { + segment = playlist.segments[i]; + if (typeof segment.end !== 'undefined') { + return { result: result + segment.end, precise: true }; + } + + result += segment.duration; + + if (typeof segment.start !== 'undefined') { + return { result: result + segment.start, precise: true }; + } + } + return { result: result, precise: false }; +}; + +/** + * walk forward until we find a duration we can use + * or return a failure + * + * @param {Playlist} playlist the playlist to walk through + * @param {Number} endSequence the mediaSequence to stop walking on + */ +var forwardDuration = function forwardDuration(playlist, endSequence) { + var result = 0; + var segment = undefined; + var i = endSequence - playlist.mediaSequence; + // Walk forward until we find the earliest segment with timeline + // information + + for (; i < playlist.segments.length; i++) { + segment = playlist.segments[i]; + if (typeof segment.start !== 'undefined') { + return { + result: segment.start - result, + precise: true + }; + } + + result += segment.duration; + + if (typeof segment.end !== 'undefined') { + return { + result: segment.end - result, + precise: true + }; + } + } + // indicate we didn't find a useful duration estimate + return { result: -1, precise: false }; +}; + +/** + * Calculate the media duration from the segments associated with a + * playlist. The duration of a subinterval of the available segments + * may be calculated by specifying an end index. + * + * @param {Object} playlist a media playlist object + * @param {Number=} endSequence an exclusive upper boundary + * for the playlist. Defaults to playlist length. + * @param {Number} expired the amount of time that has dropped + * off the front of the playlist in a live scenario + * @return {Number} the duration between the first available segment + * and end index. + */ +var intervalDuration = function intervalDuration(playlist, endSequence, expired) { + var backward = undefined; + var forward = undefined; + + if (typeof endSequence === 'undefined') { + endSequence = playlist.mediaSequence + playlist.segments.length; + } + + if (endSequence < playlist.mediaSequence) { + return 0; + } + + // do a backward walk to estimate the duration + backward = backwardDuration(playlist, endSequence); + if (backward.precise) { + // if we were able to base our duration estimate on timing + // information provided directly from the Media Source, return + // it + return backward.result; + } + + // walk forward to see if a precise duration estimate can be made + // that way + forward = forwardDuration(playlist, endSequence); + if (forward.precise) { + // we found a segment that has been buffered and so it's + // position is known precisely + return forward.result; + } + + // return the less-precise, playlist-based duration estimate + return backward.result + expired; +}; + +/** + * Calculates the duration of a playlist. If a start and end index + * are specified, the duration will be for the subset of the media + * timeline between those two indices. The total duration for live + * playlists is always Infinity. + * + * @param {Object} playlist a media playlist object + * @param {Number=} endSequence an exclusive upper + * boundary for the playlist. Defaults to the playlist media + * sequence number plus its length. + * @param {Number=} expired the amount of time that has + * dropped off the front of the playlist in a live scenario + * @return {Number} the duration between the start index and end + * index. + */ +var duration = function duration(playlist, endSequence, expired) { + if (!playlist) { + return 0; + } + + if (typeof expired !== 'number') { + expired = 0; + } + + // if a slice of the total duration is not requested, use + // playlist-level duration indicators when they're present + if (typeof endSequence === 'undefined') { + // if present, use the duration specified in the playlist + if (playlist.totalDuration) { + return playlist.totalDuration; + } + + // duration should be Infinity for live playlists + if (!playlist.endList) { + return window.Infinity; + } + } + + // calculate the total duration based on the segment durations + return intervalDuration(playlist, endSequence, expired); +}; + +exports.duration = duration; +/** + * Calculates the interval of time that is currently seekable in a + * playlist. The returned time ranges are relative to the earliest + * moment in the specified playlist that is still available. A full + * seekable implementation for live streams would need to offset + * these values by the duration of content that has expired from the + * stream. + * + * @param {Object} playlist a media playlist object + * @param {Number=} expired the amount of time that has + * dropped off the front of the playlist in a live scenario + * @return {TimeRanges} the periods of time that are valid targets + * for seeking + */ +var seekable = function seekable(playlist, expired) { + var start = undefined; + var end = undefined; + var endSequence = undefined; + + if (typeof expired !== 'number') { + expired = 0; + } + + // without segments, there are no seekable ranges + if (!playlist || !playlist.segments) { + return (0, _videoJs.createTimeRange)(); + } + // when the playlist is complete, the entire duration is seekable + if (playlist.endList) { + return (0, _videoJs.createTimeRange)(0, duration(playlist)); + } + + // live playlists should not expose three segment durations worth + // of content from the end of the playlist + // https://tools.ietf.org/html/draft-pantos-http-live-streaming-16#section-6.3.3 + start = intervalDuration(playlist, playlist.mediaSequence, expired); + endSequence = Math.max(0, playlist.segments.length - Playlist.UNSAFE_LIVE_SEGMENTS); + end = intervalDuration(playlist, playlist.mediaSequence + endSequence, expired); + return (0, _videoJs.createTimeRange)(start, end); +}; + +exports.seekable = seekable; +/** + * Determine the index of the segment that contains a specified + * playback position in a media playlist. + * + * @param {Object} playlist the media playlist to query + * @param {Number} time The number of seconds since the earliest + * possible position to determine the containing segment for + * @param {Number=} expired the duration of content, in + * seconds, that has been removed from this playlist because it + * expired + * @return {Number} The number of the media segment that contains + * that time position. + */ +var getMediaIndexForTime_ = function getMediaIndexForTime_(playlist, time, expired) { + var i = undefined; + var segment = undefined; + var originalTime = time; + var numSegments = playlist.segments.length; + var lastSegment = numSegments - 1; + var startIndex = undefined; + var endIndex = undefined; + var knownStart = undefined; + var knownEnd = undefined; + + if (!playlist) { + return 0; + } + + // when the requested position is earlier than the current set of + // segments, return the earliest segment index + if (time < 0) { + return 0; + } + + expired = expired || 0; + + // find segments with known timing information that bound the + // target time + for (i = 0; i < numSegments; i++) { + segment = playlist.segments[i]; + if (segment.end) { + if (segment.end > time) { + knownEnd = segment.end; + endIndex = i; + break; + } else { + knownStart = segment.end; + startIndex = i + 1; + } + } + } + + // time was equal to or past the end of the last segment in the playlist + if (startIndex === numSegments) { + return numSegments; + } + + // use the bounds we just found and playlist information to + // estimate the segment that contains the time we are looking for + if (typeof startIndex !== 'undefined') { + // We have a known-start point that is before our desired time so + // walk from that point forwards + time = time - knownStart; + for (i = startIndex; i < (endIndex || numSegments); i++) { + segment = playlist.segments[i]; + time -= segment.duration; + + if (time < 0) { + return i; + } + } + + if (i >= endIndex) { + // We haven't found a segment but we did hit a known end point + // so fallback to interpolating between the segment index + // based on the known span of the timeline we are dealing with + // and the number of segments inside that span + return startIndex + Math.floor((originalTime - knownStart) / (knownEnd - knownStart) * (endIndex - startIndex)); + } + + // We _still_ haven't found a segment so load the last one + return lastSegment; + } else if (typeof endIndex !== 'undefined') { + // We _only_ have a known-end point that is after our desired time so + // walk from that point backwards + time = knownEnd - time; + for (i = endIndex; i >= 0; i--) { + segment = playlist.segments[i]; + time -= segment.duration; + + if (time < 0) { + return i; + } + } + + // We haven't found a segment so load the first one if time is zero + if (time === 0) { + return 0; + } + return -1; + } + // We known nothing so walk from the front of the playlist, + // subtracting durations until we find a segment that contains + // time and return it + time = time - expired; + + if (time < 0) { + return -1; + } + + for (i = 0; i < numSegments; i++) { + segment = playlist.segments[i]; + time -= segment.duration; + if (time < 0) { + return i; + } + } + // We are out of possible candidates so load the last one... + // The last one is the least likely to overlap a buffer and therefore + // the one most likely to tell us something about the timeline + return lastSegment; +}; + +exports.getMediaIndexForTime_ = getMediaIndexForTime_; +Playlist.duration = duration; +Playlist.seekable = seekable; +Playlist.getMediaIndexForTime_ = getMediaIndexForTime_; + +// exports +exports['default'] = Playlist; +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],11:[function(require,module,exports){ +(function (global){ +/** + * ranges + * + * Utilities for working with TimeRanges. + * + */ + +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })(); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); + +var _videoJs2 = _interopRequireDefault(_videoJs); + +// Fudge factor to account for TimeRanges rounding +var TIME_FUDGE_FACTOR = 1 / 30; + +/** + * Clamps a value to within a range + * @param {Number} num - the value to clamp + * @param {Number} start - the start of the range to clamp within, inclusive + * @param {Number} end - the end of the range to clamp within, inclusive + * @return {Number} + */ +var clamp = function clamp(num, _ref) { + var _ref2 = _slicedToArray(_ref, 2); + + var start = _ref2[0]; + var end = _ref2[1]; + + return Math.min(Math.max(start, num), end); +}; +var filterRanges = function filterRanges(timeRanges, predicate) { + var results = []; + var i = undefined; + + if (timeRanges && timeRanges.length) { + // Search for ranges that match the predicate + for (i = 0; i < timeRanges.length; i++) { + if (predicate(timeRanges.start(i), timeRanges.end(i))) { + results.push([timeRanges.start(i), timeRanges.end(i)]); + } + } + } + + return _videoJs2['default'].createTimeRanges(results); +}; + +/** + * Attempts to find the buffered TimeRange that contains the specified + * time. + * @param {TimeRanges} buffered - the TimeRanges object to query + * @param {number} time - the time to filter on. + * @returns {TimeRanges} a new TimeRanges object + */ +var findRange = function findRange(buffered, time) { + return filterRanges(buffered, function (start, end) { + return start - TIME_FUDGE_FACTOR <= time && end + TIME_FUDGE_FACTOR >= time; + }); +}; + +/** + * Returns the TimeRanges that begin at or later than the specified + * time. + * @param {TimeRanges} timeRanges - the TimeRanges object to query + * @param {number} time - the time to filter on. + * @returns {TimeRanges} a new TimeRanges object. + */ +var findNextRange = function findNextRange(timeRanges, time) { + return filterRanges(timeRanges, function (start) { + return start - TIME_FUDGE_FACTOR >= time; + }); +}; + +/** + * Search for a likely end time for the segment that was just appened + * based on the state of the `buffered` property before and after the + * append. If we fin only one such uncommon end-point return it. + * @param {TimeRanges} original - the buffered time ranges before the update + * @param {TimeRanges} update - the buffered time ranges after the update + * @returns {Number|null} the end time added between `original` and `update`, + * or null if one cannot be unambiguously determined. + */ +var findSoleUncommonTimeRangesEnd = function findSoleUncommonTimeRangesEnd(original, update) { + var i = undefined; + var start = undefined; + var end = undefined; + var result = []; + var edges = []; + + // In order to qualify as a possible candidate, the end point must: + // 1) Not have already existed in the `original` ranges + // 2) Not result from the shrinking of a range that already existed + // in the `original` ranges + // 3) Not be contained inside of a range that existed in `original` + var overlapsCurrentEnd = function overlapsCurrentEnd(span) { + return span[0] <= end && span[1] >= end; + }; + + if (original) { + // Save all the edges in the `original` TimeRanges object + for (i = 0; i < original.length; i++) { + start = original.start(i); + end = original.end(i); + + edges.push([start, end]); + } + } + + if (update) { + // Save any end-points in `update` that are not in the `original` + // TimeRanges object + for (i = 0; i < update.length; i++) { + start = update.start(i); + end = update.end(i); + + if (edges.some(overlapsCurrentEnd)) { + continue; + } + + // at this point it must be a unique non-shrinking end edge + result.push(end); + } + } + + // we err on the side of caution and return null if didn't find + // exactly *one* differing end edge in the search above + if (result.length !== 1) { + return null; + } + + return result[0]; +}; + +/** + * Calculate the intersection of two TimeRanges + * @param {TimeRanges} bufferA + * @param {TimeRanges} bufferB + * @returns {TimeRanges} The interesection of `bufferA` with `bufferB` + */ +var bufferIntersection = function bufferIntersection(bufferA, bufferB) { + var start = null; + var end = null; + var arity = 0; + var extents = []; + var ranges = []; + + if (!bufferA || !bufferA.length || !bufferB || !bufferB.length) { + return _videoJs2['default'].createTimeRange(); + } + + // Handle the case where we have both buffers and create an + // intersection of the two + var count = bufferA.length; + + // A) Gather up all start and end times + while (count--) { + extents.push({ time: bufferA.start(count), type: 'start' }); + extents.push({ time: bufferA.end(count), type: 'end' }); + } + count = bufferB.length; + while (count--) { + extents.push({ time: bufferB.start(count), type: 'start' }); + extents.push({ time: bufferB.end(count), type: 'end' }); + } + // B) Sort them by time + extents.sort(function (a, b) { + return a.time - b.time; + }); + + // C) Go along one by one incrementing arity for start and decrementing + // arity for ends + for (count = 0; count < extents.length; count++) { + if (extents[count].type === 'start') { + arity++; + + // D) If arity is ever incremented to 2 we are entering an + // overlapping range + if (arity === 2) { + start = extents[count].time; + } + } else if (extents[count].type === 'end') { + arity--; + + // E) If arity is ever decremented to 1 we leaving an + // overlapping range + if (arity === 1) { + end = extents[count].time; + } + } + + // F) Record overlapping ranges + if (start !== null && end !== null) { + ranges.push([start, end]); + start = null; + end = null; + } + } + + return _videoJs2['default'].createTimeRanges(ranges); +}; + +/** + * Calculates the percentage of `segmentRange` that overlaps the + * `buffered` time ranges. + * @param {TimeRanges} segmentRange - the time range that the segment + * covers adjusted according to currentTime + * @param {TimeRanges} referenceRange - the original time range that the + * segment covers + * @param {Number} currentTime - time in seconds where the current playback + * is at + * @param {TimeRanges} buffered - the currently buffered time ranges + * @returns {Number} percent of the segment currently buffered + */ +var calculateBufferedPercent = function calculateBufferedPercent(adjustedRange, referenceRange, currentTime, buffered) { + var referenceDuration = referenceRange.end(0) - referenceRange.start(0); + var adjustedDuration = adjustedRange.end(0) - adjustedRange.start(0); + var bufferMissingFromAdjusted = referenceDuration - adjustedDuration; + var adjustedIntersection = bufferIntersection(adjustedRange, buffered); + var referenceIntersection = bufferIntersection(referenceRange, buffered); + var adjustedOverlap = 0; + var referenceOverlap = 0; + + var count = adjustedIntersection.length; + + while (count--) { + adjustedOverlap += adjustedIntersection.end(count) - adjustedIntersection.start(count); + + // If the current overlap segment starts at currentTime, then increase the + // overlap duration so that it actually starts at the beginning of referenceRange + // by including the difference between the two Range's durations + // This is a work around for the way Flash has no buffer before currentTime + if (adjustedIntersection.start(count) === currentTime) { + adjustedOverlap += bufferMissingFromAdjusted; + } + } + + count = referenceIntersection.length; + + while (count--) { + referenceOverlap += referenceIntersection.end(count) - referenceIntersection.start(count); + } + + // Use whichever value is larger for the percentage-buffered since that value + // is likely more accurate because the only way + return Math.max(adjustedOverlap, referenceOverlap) / referenceDuration * 100; +}; + +/** + * Return the amount of a range specified by the startOfSegment and segmentDuration + * overlaps the current buffered content. + * + * @param {Number} startOfSegment - the time where the segment begins + * @param {Number} segmentDuration - the duration of the segment in seconds + * @param {Number} currentTime - time in seconds where the current playback + * is at + * @param {TimeRanges} buffered - the state of the buffer + * @returns {Number} percentage of the segment's time range that is + * already in `buffered` + */ +var getSegmentBufferedPercent = function getSegmentBufferedPercent(startOfSegment, segmentDuration, currentTime, buffered) { + var endOfSegment = startOfSegment + segmentDuration; + + // The entire time range of the segment + var originalSegmentRange = _videoJs2['default'].createTimeRanges([[startOfSegment, endOfSegment]]); + + // The adjusted segment time range that is setup such that it starts + // no earlier than currentTime + // Flash has no notion of a back-buffer so adjustedSegmentRange adjusts + // for that and the function will still return 100% if a only half of a + // segment is actually in the buffer as long as the currentTime is also + // half-way through the segment + var adjustedSegmentRange = _videoJs2['default'].createTimeRanges([[clamp(startOfSegment, [currentTime, endOfSegment]), endOfSegment]]); + + // This condition happens when the currentTime is beyond the segment's + // end time + if (adjustedSegmentRange.start(0) === adjustedSegmentRange.end(0)) { + return 0; + } + + var percent = calculateBufferedPercent(adjustedSegmentRange, originalSegmentRange, currentTime, buffered); + + // If the segment is reported as having a zero duration, return 0% + // since it is likely that we will need to fetch the segment + if (isNaN(percent) || percent === Infinity || percent === -Infinity) { + return 0; + } + + return percent; +}; + +exports['default'] = { + findRange: findRange, + findNextRange: findNextRange, + findSoleUncommonTimeRangesEnd: findSoleUncommonTimeRangesEnd, + getSegmentBufferedPercent: getSegmentBufferedPercent, + TIME_FUDGE_FACTOR: TIME_FUDGE_FACTOR +}; +module.exports = exports['default']; +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],12:[function(require,module,exports){ +/** + * Enable/disable playlist function. It is intended to have the first two + * arguments partially-applied in order to create the final per-playlist + * function. + * + * @param {PlaylistLoader} playlist - The rendition or media-playlist + * @param {Function} changePlaylistFn - A function to be called after a + * playlist's enabled-state has been changed. Will NOT be called if a + * playlist's enabled-state is unchanged + * @param {Boolean=} enable - Value to set the playlist enabled-state to + * or if undefined returns the current enabled-state for the playlist + * @return {Boolean} The current enabled-state of the playlist + */ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +var enableFunction = function enableFunction(playlist, changePlaylistFn, enable) { + var currentlyEnabled = typeof playlist.excludeUntil === 'undefined' || playlist.excludeUntil <= Date.now(); + + if (typeof enable === 'undefined') { + return currentlyEnabled; + } + + if (enable !== currentlyEnabled) { + if (enable) { + delete playlist.excludeUntil; + } else { + playlist.excludeUntil = Infinity; + } + + // Ensure the outside world knows about our changes + changePlaylistFn(); + } + + return enable; +}; + +/** + * The representation object encapsulates the publicly visible information + * in a media playlist along with a setter/getter-type function (enabled) + * for changing the enabled-state of a particular playlist entry + * + * @class Representation + */ + +var Representation = function Representation(hlsHandler, playlist, id) { + _classCallCheck(this, Representation); + + // Get a reference to a bound version of fastQualityChange_ + var fastChangeFunction = hlsHandler.masterPlaylistController_.fastQualityChange_.bind(hlsHandler.masterPlaylistController_); + + // Carefully descend into the playlist's attributes since most + // properties are optional + if (playlist.attributes) { + var attributes = playlist.attributes; + + if (attributes.RESOLUTION) { + var resolution = attributes.RESOLUTION; + + this.width = resolution.width; + this.height = resolution.height; + } + + this.bandwidth = attributes.BANDWIDTH; + } + + // The id is simply the ordinality of the media playlist + // within the master playlist + this.id = id; + + // Partially-apply the enableFunction to create a playlist- + // specific variant + this.enabled = enableFunction.bind(this, playlist, fastChangeFunction); +} + +/** + * A mixin function that adds the `representations` api to an instance + * of the HlsHandler class + * @param {HlsHandler} hlsHandler - An instance of HlsHandler to add the + * representation API into + */ +; + +var renditionSelectionMixin = function renditionSelectionMixin(hlsHandler) { + var playlists = hlsHandler.playlists; + + // Add a single API-specific function to the HlsHandler instance + hlsHandler.representations = function () { + return playlists.master.playlists.map(function (e, i) { + return new Representation(hlsHandler, e, i); + }); + }; +}; + +exports['default'] = renditionSelectionMixin; +module.exports = exports['default']; +},{}],13:[function(require,module,exports){ +/** + * @file resolve-url.js + */ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _globalDocument = require('global/document'); + +var _globalDocument2 = _interopRequireDefault(_globalDocument); + +/** + * Constructs a new URI by interpreting a path relative to another + * URI. + * + * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue + * @param {String} basePath a relative or absolute URI + * @param {String} path a path part to combine with the base + * @return {String} a URI that is equivalent to composing `base` + * with `path` + */ +var resolveUrl = function resolveUrl(basePath, path) { + // use the base element to get the browser to handle URI resolution + var oldBase = _globalDocument2['default'].querySelector('base'); + var docHead = _globalDocument2['default'].querySelector('head'); + var a = _globalDocument2['default'].createElement('a'); + var base = oldBase; + var oldHref = undefined; + var result = undefined; + + // prep the document + if (oldBase) { + oldHref = oldBase.href; + } else { + base = docHead.appendChild(_globalDocument2['default'].createElement('base')); + } + + base.href = basePath; + a.href = path; + result = a.href; + + // clean up + if (oldBase) { + oldBase.href = oldHref; + } else { + docHead.removeChild(base); + } + return result; +}; + +exports['default'] = resolveUrl; +module.exports = exports['default']; +},{"global/document":19}],14:[function(require,module,exports){ +(function (global){ +/** + * @file segment-loader.js + */ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _ranges = require('./ranges'); + +var _ranges2 = _interopRequireDefault(_ranges); + +var _playlist = require('./playlist'); + +var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); + +var _videoJs2 = _interopRequireDefault(_videoJs); + +var _sourceUpdater = require('./source-updater'); + +var _sourceUpdater2 = _interopRequireDefault(_sourceUpdater); + +var _decrypter = require('./decrypter'); + +var _config = require('./config'); + +var _config2 = _interopRequireDefault(_config); + +// in ms +var CHECK_BUFFER_DELAY = 500; + +/** + * Updates segment with information about its end-point in time and, optionally, + * the segment duration if we have enough information to determine a segment duration + * accurately. + * + * @param {Object} playlist a media playlist object + * @param {Number} segmentIndex the index of segment we last appended + * @param {Number} segmentEnd the known of the segment referenced by segmentIndex + */ +var updateSegmentMetadata = function updateSegmentMetadata(playlist, segmentIndex, segmentEnd) { + if (!playlist) { + return false; + } + + var segment = playlist.segments[segmentIndex]; + var previousSegment = playlist.segments[segmentIndex - 1]; + + if (segmentEnd && segment) { + segment.end = segmentEnd; + + // fix up segment durations based on segment end data + if (!previousSegment) { + // first segment is always has a start time of 0 making its duration + // equal to the segment end + segment.duration = segment.end; + } else if (previousSegment.end) { + segment.duration = segment.end - previousSegment.end; + } + return true; + } + return false; +}; + +/** + * Determines if we should call endOfStream on the media source based + * on the state of the buffer or if appened segment was the final + * segment in the playlist. + * + * @param {Object} playlist a media playlist object + * @param {Object} mediaSource the MediaSource object + * @param {Number} segmentIndex the index of segment we last appended + * @param {Object} currentBuffered buffered region that currentTime resides in + * @returns {Boolean} do we need to call endOfStream on the MediaSource + */ +var detectEndOfStream = function detectEndOfStream(playlist, mediaSource, segmentIndex, currentBuffered) { + if (!playlist) { + return false; + } + + var segments = playlist.segments; + + // determine a few boolean values to help make the branch below easier + // to read + var appendedLastSegment = segmentIndex === segments.length - 1; + var bufferedToEnd = currentBuffered.length && segments[segments.length - 1].end <= currentBuffered.end(0); + + // if we've buffered to the end of the video, we need to call endOfStream + // so that MediaSources can trigger the `ended` event when it runs out of + // buffered data instead of waiting for me + return playlist.endList && mediaSource.readyState === 'open' && (appendedLastSegment || bufferedToEnd); +}; + +/** + * Turns segment byterange into a string suitable for use in + * HTTP Range requests + */ +var byterangeStr = function byterangeStr(byterange) { + var byterangeStart = undefined; + var byterangeEnd = undefined; + + // `byterangeEnd` is one less than `offset + length` because the HTTP range + // header uses inclusive ranges + byterangeEnd = byterange.offset + byterange.length - 1; + byterangeStart = byterange.offset; + return 'bytes=' + byterangeStart + '-' + byterangeEnd; +}; + +/** + * Defines headers for use in the xhr request for a particular segment. + */ +var segmentXhrHeaders = function segmentXhrHeaders(segment) { + var headers = {}; + + if ('byterange' in segment) { + headers.Range = byterangeStr(segment.byterange); + } + return headers; +}; + +/** + * An object that manages segment loading and appending. + * + * @class SegmentLoader + * @param {Object} options required and optional options + * @extends videojs.EventTarget + */ + +var SegmentLoader = (function (_videojs$EventTarget) { + _inherits(SegmentLoader, _videojs$EventTarget); + + function SegmentLoader(options) { + _classCallCheck(this, SegmentLoader); + + _get(Object.getPrototypeOf(SegmentLoader.prototype), 'constructor', this).call(this); + var settings = undefined; + + // check pre-conditions + if (!options) { + throw new TypeError('Initialization options are required'); + } + if (typeof options.currentTime !== 'function') { + throw new TypeError('No currentTime getter specified'); + } + if (!options.mediaSource) { + throw new TypeError('No MediaSource specified'); + } + settings = _videoJs2['default'].mergeOptions(_videoJs2['default'].options.hls, options); + + // public properties + this.state = 'INIT'; + this.bandwidth = settings.bandwidth; + this.roundTrip = NaN; + this.resetStats_(); + + // private properties + this.hasPlayed_ = settings.hasPlayed; + this.currentTime_ = settings.currentTime; + this.seekable_ = settings.seekable; + this.seeking_ = settings.seeking; + this.setCurrentTime_ = settings.setCurrentTime; + this.mediaSource_ = settings.mediaSource; + this.withCredentials_ = settings.withCredentials; + this.checkBufferTimeout_ = null; + this.error_ = void 0; + this.expired_ = 0; + this.timeCorrection_ = 0; + this.currentTimeline_ = -1; + this.xhr_ = null; + this.pendingSegment_ = null; + this.sourceUpdater_ = null; + this.hls_ = settings.hls; + } + + /** + * reset all of our media stats + * + * @private + */ + + _createClass(SegmentLoader, [{ + key: 'resetStats_', + value: function resetStats_() { + this.mediaBytesTransferred = 0; + this.mediaRequests = 0; + this.mediaTransferDuration = 0; + } + + /** + * dispose of the SegmentLoader and reset to the default state + */ + }, { + key: 'dispose', + value: function dispose() { + this.state = 'DISPOSED'; + this.abort_(); + if (this.sourceUpdater_) { + this.sourceUpdater_.dispose(); + } + this.resetStats_(); + } + + /** + * abort anything that is currently doing on with the SegmentLoader + * and reset to a default state + */ + }, { + key: 'abort', + value: function abort() { + if (this.state !== 'WAITING') { + return; + } + + this.abort_(); + + // don't wait for buffer check timeouts to begin fetching the + // next segment + if (!this.paused()) { + this.state = 'READY'; + this.fillBuffer_(); + } + } + + /** + * set an error on the segment loader and null out any pending segements + * + * @param {Error} error the error to set on the SegmentLoader + * @return {Error} the error that was set or that is currently set + */ + }, { + key: 'error', + value: function error(_error) { + if (typeof _error !== 'undefined') { + this.error_ = _error; + } + + this.pendingSegment_ = null; + return this.error_; + } + + /** + * load a playlist and start to fill the buffer + */ + }, { + key: 'load', + value: function load() { + this.monitorBuffer_(); + + // if we don't have a playlist yet, keep waiting for one to be + // specified + if (!this.playlist_) { + return; + } + + // if we're in the middle of processing a segment already, don't + // kick off an additional segment request + if (!this.sourceUpdater_ || this.state !== 'READY' && this.state !== 'INIT') { + return; + } + + this.state = 'READY'; + this.fillBuffer_(); + } + + /** + * set a playlist on the segment loader + * + * @param {PlaylistLoader} media the playlist to set on the segment loader + */ + }, { + key: 'playlist', + value: function playlist(media) { + this.playlist_ = media; + // if we were unpaused but waiting for a playlist, start + // buffering now + if (this.sourceUpdater_ && media && this.state === 'INIT' && !this.paused()) { + this.state = 'READY'; + return this.fillBuffer_(); + } + } + + /** + * Prevent the loader from fetching additional segments. If there + * is a segment request outstanding, it will finish processing + * before the loader halts. A segment loader can be unpaused by + * calling load(). + */ + }, { + key: 'pause', + value: function pause() { + if (this.checkBufferTimeout_) { + window.clearTimeout(this.checkBufferTimeout_); + + this.checkBufferTimeout_ = null; + } + } + + /** + * Returns whether the segment loader is fetching additional + * segments when given the opportunity. This property can be + * modified through calls to pause() and load(). + */ + }, { + key: 'paused', + value: function paused() { + return this.checkBufferTimeout_ === null; + } + + /** + * setter for expired time on the SegmentLoader + * + * @param {Number} expired the exired time to set + */ + }, { + key: 'expired', + value: function expired(_expired) { + this.expired_ = _expired; + } + + /** + * create/set the following mimetype on the SourceBuffer through a + * SourceUpdater + * + * @param {String} mimeType the mime type string to use + */ + }, { + key: 'mimeType', + value: function mimeType(_mimeType) { + // TODO Allow source buffers to be re-created with different mime-types + if (!this.sourceUpdater_) { + this.sourceUpdater_ = new _sourceUpdater2['default'](this.mediaSource_, _mimeType); + this.clearBuffer(); + + // if we were unpaused but waiting for a sourceUpdater, start + // buffering now + if (this.playlist_ && this.state === 'INIT' && !this.paused()) { + this.state = 'READY'; + return this.fillBuffer_(); + } + } + } + + /** + * asynchronously/recursively monitor the buffer + * + * @private + */ + }, { + key: 'monitorBuffer_', + value: function monitorBuffer_() { + if (this.state === 'READY') { + this.fillBuffer_(); + } + + if (this.checkBufferTimeout_) { + window.clearTimeout(this.checkBufferTimeout_); + } + + this.checkBufferTimeout_ = window.setTimeout(this.monitorBuffer_.bind(this), CHECK_BUFFER_DELAY); + } + + /** + * Determines what segment request should be made, given current + * playback state. + * + * @param {TimeRanges} buffered - the state of the buffer + * @param {Object} playlist - the playlist object to fetch segments from + * @param {Number} currentTime - the playback position in seconds + * @returns {Object} a segment info object that describes the + * request that should be made or null if no request is necessary + */ + }, { + key: 'checkBuffer_', + value: function checkBuffer_(buffered, playlist, currentTime) { + var currentBuffered = _ranges2['default'].findRange(buffered, currentTime); + + // There are times when MSE reports the first segment as starting a + // little after 0-time so add a fudge factor to try and fix those cases + // or we end up fetching the same first segment over and over + if (currentBuffered.length === 0 && currentTime === 0) { + currentBuffered = _ranges2['default'].findRange(buffered, currentTime + _ranges2['default'].TIME_FUDGE_FACTOR); + } + + var bufferedTime = undefined; + var currentBufferedEnd = undefined; + var timestampOffset = this.sourceUpdater_.timestampOffset(); + var segment = undefined; + var mediaIndex = undefined; + + if (!playlist.segments.length) { + return; + } + + if (currentBuffered.length === 0) { + // find the segment containing currentTime + mediaIndex = (0, _playlist.getMediaIndexForTime_)(playlist, currentTime + this.timeCorrection_, this.expired_); + } else { + // find the segment adjacent to the end of the current + // buffered region + currentBufferedEnd = currentBuffered.end(0); + bufferedTime = Math.max(0, currentBufferedEnd - currentTime); + + // if the video has not yet played only, and we already have + // one segment downloaded do nothing + if (!this.hasPlayed_() && bufferedTime >= 1) { + return null; + } + + // if there is plenty of content buffered, and the video has + // been played before relax for awhile + if (this.hasPlayed_() && bufferedTime >= _config2['default'].GOAL_BUFFER_LENGTH) { + return null; + } + mediaIndex = (0, _playlist.getMediaIndexForTime_)(playlist, currentBufferedEnd + this.timeCorrection_, this.expired_); + } + + if (mediaIndex < 0 || mediaIndex === playlist.segments.length) { + return null; + } + + segment = playlist.segments[mediaIndex]; + var startOfSegment = (0, _playlist.duration)(playlist, playlist.mediaSequence + mediaIndex, this.expired_); + + // We will need to change timestampOffset of the sourceBuffer if either of + // the following conditions are true: + // - The segment.timeline !== this.currentTimeline + // (we are crossing a discontinuity somehow) + // - The "timestampOffset" for the start of this segment is less than + // the currently set timestampOffset + if (segment.timeline !== this.currentTimeline_ || startOfSegment < this.sourceUpdater_.timestampOffset()) { + timestampOffset = startOfSegment; + } + + return { + // resolve the segment URL relative to the playlist + uri: segment.resolvedUri, + // the segment's mediaIndex at the time it was requested + mediaIndex: mediaIndex, + // the segment's playlist + playlist: playlist, + // unencrypted bytes of the segment + bytes: null, + // when a key is defined for this segment, the encrypted bytes + encryptedBytes: null, + // the state of the buffer before a segment is appended will be + // stored here so that the actual segment duration can be + // determined after it has been appended + buffered: null, + // The target timestampOffset for this segment when we append it + // to the source buffer + timestampOffset: timestampOffset, + // The timeline that the segment is in + timeline: segment.timeline, + // The expected duration of the segment in seconds + duration: segment.duration + }; + } + + /** + * abort all pending xhr requests and null any pending segements + * + * @private + */ + }, { + key: 'abort_', + value: function abort_() { + if (this.xhr_) { + this.xhr_.abort(); + } + + // clear out the segment being processed + this.pendingSegment_ = null; + } + + /** + * fill the buffer with segements unless the + * sourceBuffers are currently updating + * + * @private + */ + }, { + key: 'fillBuffer_', + value: function fillBuffer_() { + if (this.sourceUpdater_.updating()) { + return; + } + + // see if we need to begin loading immediately + var request = this.checkBuffer_(this.sourceUpdater_.buffered(), this.playlist_, this.currentTime_(), this.timestampOffset_); + + if (!request) { + return; + } + + if (request.mediaIndex === this.playlist_.segments.length - 1 && this.mediaSource_.readyState === 'ended' && !this.seeking_()) { + return; + } + + var segment = this.playlist_.segments[request.mediaIndex]; + var startOfSegment = (0, _playlist.duration)(this.playlist_, this.playlist_.mediaSequence + request.mediaIndex, this.expired_); + + // Sanity check the segment-index determining logic by calcuating the + // percentage of the chosen segment that is buffered. If more than 90% + // of the segment is buffered then fetching it will likely not help in + // any way + var percentBuffered = _ranges2['default'].getSegmentBufferedPercent(startOfSegment, segment.duration, this.currentTime_(), this.sourceUpdater_.buffered()); + + if (percentBuffered >= 90) { + // Increment the timeCorrection_ variable to push the fetcher forward + // in time and hopefully skip any gaps or flaws in our understanding + // of the media + var correctionApplied = this.incrementTimeCorrection_(this.playlist_.targetDuration / 2, 1); + + if (correctionApplied && !this.paused()) { + this.fillBuffer_(); + } + + return; + } + + this.loadSegment_(request); + } + + /** + * load a specific segment from a request into the buffer + * + * @private + */ + }, { + key: 'loadSegment_', + value: function loadSegment_(segmentInfo) { + var segment = undefined; + var requestTimeout = undefined; + var keyXhr = undefined; + var segmentXhr = undefined; + var seekable = this.seekable_(); + var currentTime = this.currentTime_(); + var removeToTime = 0; + + // Chrome has a hard limit of 150mb of + // buffer and a very conservative "garbage collector" + // We manually clear out the old buffer to ensure + // we don't trigger the QuotaExceeded error + // on the source buffer during subsequent appends + + // If we have a seekable range use that as the limit for what can be removed safely + // otherwise remove anything older than 1 minute before the current play head + if (seekable.length && seekable.start(0) > 0 && seekable.start(0) < currentTime) { + removeToTime = seekable.start(0); + } else { + removeToTime = currentTime - 60; + } + + if (removeToTime > 0) { + this.sourceUpdater_.remove(0, removeToTime); + } + + segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex]; + // Set xhr timeout to 150% of the segment duration to allow us + // some time to switch renditions in the event of a catastrophic + // decrease in network performance or a server issue. + requestTimeout = segment.duration * 1.5 * 1000; + + if (segment.key) { + keyXhr = this.hls_.xhr({ + uri: segment.key.resolvedUri, + responseType: 'arraybuffer', + withCredentials: this.withCredentials_, + timeout: requestTimeout + }, this.handleResponse_.bind(this)); + } + this.pendingSegment_ = segmentInfo; + segmentXhr = this.hls_.xhr({ + uri: segmentInfo.uri, + responseType: 'arraybuffer', + withCredentials: this.withCredentials_, + timeout: requestTimeout, + headers: segmentXhrHeaders(segment) + }, this.handleResponse_.bind(this)); + + this.xhr_ = { + keyXhr: keyXhr, + segmentXhr: segmentXhr, + abort: function abort() { + if (this.segmentXhr) { + // Prevent error handler from running. + this.segmentXhr.onreadystatechange = null; + this.segmentXhr.abort(); + this.segmentXhr = null; + } + if (this.keyXhr) { + // Prevent error handler from running. + this.keyXhr.onreadystatechange = null; + this.keyXhr.abort(); + this.keyXhr = null; + } + } + }; + + this.state = 'WAITING'; + } + + /** + * triggered when a segment response is received + * + * @private + */ + }, { + key: 'handleResponse_', + value: function handleResponse_(error, request) { + var segmentInfo = undefined; + var segment = undefined; + var keyXhrRequest = undefined; + var view = undefined; + + // timeout of previously aborted request + if (!this.xhr_ || request !== this.xhr_.segmentXhr && request !== this.xhr_.keyXhr) { + return; + } + + segmentInfo = this.pendingSegment_; + segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex]; + + // if a request times out, reset bandwidth tracking + if (request.timedout) { + this.abort_(); + this.bandwidth = 1; + this.roundTrip = NaN; + this.state = 'READY'; + return this.trigger('progress'); + } + + // trigger an event for other errors + if (!request.aborted && error) { + // abort will clear xhr_ + keyXhrRequest = this.xhr_.keyXhr; + this.abort_(); + this.error({ + status: request.status, + message: request === keyXhrRequest ? 'HLS key request error at URL: ' + segment.key.uri : 'HLS segment request error at URL: ' + segmentInfo.uri, + code: 2, + xhr: request + }); + this.state = 'READY'; + this.pause(); + return this.trigger('error'); + } + + // stop processing if the request was aborted + if (!request.response) { + this.abort_(); + return; + } + + if (request === this.xhr_.segmentXhr) { + // the segment request is no longer outstanding + this.xhr_.segmentXhr = null; + + // calculate the download bandwidth based on segment request + this.roundTrip = request.roundTripTime; + this.bandwidth = request.bandwidth; + this.mediaBytesTransferred += request.bytesReceived || 0; + this.mediaRequests += 1; + this.mediaTransferDuration += request.roundTripTime || 0; + + if (segment.key) { + segmentInfo.encryptedBytes = new Uint8Array(request.response); + } else { + segmentInfo.bytes = new Uint8Array(request.response); + } + } + + if (request === this.xhr_.keyXhr) { + keyXhrRequest = this.xhr_.segmentXhr; + // the key request is no longer outstanding + this.xhr_.keyXhr = null; + + if (request.response.byteLength !== 16) { + this.abort_(); + this.error({ + status: request.status, + message: 'Invalid HLS key at URL: ' + segment.key.uri, + code: 2, + xhr: request + }); + this.state = 'READY'; + this.pause(); + return this.trigger('error'); + } + + view = new DataView(request.response); + segment.key.bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]); + + // if the media sequence is greater than 2^32, the IV will be incorrect + // assuming 10s segments, that would be about 1300 years + segment.key.iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]); + } + + if (!this.xhr_.segmentXhr && !this.xhr_.keyXhr) { + this.xhr_ = null; + this.processResponse_(); + } + } + + /** + * clear anything that is currently in the buffer and throw it away + */ + }, { + key: 'clearBuffer', + value: function clearBuffer() { + if (this.sourceUpdater_ && this.sourceUpdater_.buffered().length) { + this.sourceUpdater_.remove(0, Infinity); + } + } + + /** + * Decrypt the segment that is being loaded if necessary + * + * @private + */ + }, { + key: 'processResponse_', + value: function processResponse_() { + var segmentInfo = undefined; + var segment = undefined; + + this.state = 'DECRYPTING'; + + segmentInfo = this.pendingSegment_; + segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex]; + + if (segment.key) { + // this is an encrypted segment + // incrementally decrypt the segment + /* eslint-disable no-new, handle-callback-err */ + new _decrypter.Decrypter(segmentInfo.encryptedBytes, segment.key.bytes, segment.key.iv, (function (err, bytes) { + // err always null + segmentInfo.bytes = bytes; + this.handleSegment_(); + }).bind(this)); + /* eslint-enable */ + } else { + this.handleSegment_(); + } + } + + /** + * append a decrypted segement to the SourceBuffer through a SourceUpdater + * + * @private + */ + }, { + key: 'handleSegment_', + value: function handleSegment_() { + var segmentInfo = undefined; + + this.state = 'APPENDING'; + segmentInfo = this.pendingSegment_; + segmentInfo.buffered = this.sourceUpdater_.buffered(); + this.currentTimeline_ = segmentInfo.timeline; + + if (segmentInfo.timestampOffset !== this.sourceUpdater_.timestampOffset()) { + this.sourceUpdater_.timestampOffset(segmentInfo.timestampOffset); + } + + this.sourceUpdater_.appendBuffer(segmentInfo.bytes, this.handleUpdateEnd_.bind(this)); + } + + /** + * callback to run when appendBuffer is finished. detects if we are + * in a good state to do things with the data we got, or if we need + * to wait for more + * + * @private + */ + }, { + key: 'handleUpdateEnd_', + value: function handleUpdateEnd_() { + var segmentInfo = this.pendingSegment_; + var currentTime = this.currentTime_(); + + this.pendingSegment_ = null; + + // add segment metadata if it we have gained information during the + // last append + var timelineUpdated = this.updateTimeline_(segmentInfo); + + this.trigger('progress'); + + var currentMediaIndex = segmentInfo.mediaIndex; + + currentMediaIndex += segmentInfo.playlist.mediaSequence - this.playlist_.mediaSequence; + + var currentBuffered = _ranges2['default'].findRange(this.sourceUpdater_.buffered(), currentTime); + + // any time an update finishes and the last segment is in the + // buffer, end the stream. this ensures the "ended" event will + // fire if playback reaches that point. + var isEndOfStream = detectEndOfStream(segmentInfo.playlist, this.mediaSource_, currentMediaIndex, currentBuffered); + + if (isEndOfStream) { + this.mediaSource_.endOfStream(); + } + + // when seeking to the beginning of the seekable range, it's + // possible that imprecise timing information may cause the seek to + // end up earlier than the start of the range + // in that case, seek again + var seekable = this.seekable_(); + var next = _ranges2['default'].findNextRange(this.sourceUpdater_.buffered(), currentTime); + + if (this.seeking_() && currentBuffered.length === 0) { + if (seekable.length && currentTime < seekable.start(0)) { + + if (next.length) { + _videoJs2['default'].log('tried seeking to', currentTime, 'but that was too early, retrying at', next.start(0)); + this.setCurrentTime_(next.start(0) + _ranges2['default'].TIME_FUDGE_FACTOR); + } + } + } + + this.state = 'READY'; + + if (timelineUpdated) { + this.timeCorrection_ = 0; + if (!this.paused()) { + this.fillBuffer_(); + } + return; + } + + // the last segment append must have been entirely in the + // already buffered time ranges. adjust the timeCorrection + // offset to fetch forward until we find a segment that adds + // to the buffered time ranges and improves subsequent media + // index calculations. + var correctionApplied = this.incrementTimeCorrection_(segmentInfo.duration, 4); + + if (correctionApplied && !this.paused()) { + this.fillBuffer_(); + } + } + + /** + * annotate the segment with any start and end time information + * added by the media processing + * + * @private + * @param {Object} segmentInfo annotate a segment with time info + */ + }, { + key: 'updateTimeline_', + value: function updateTimeline_(segmentInfo) { + var segment = undefined; + var segmentEnd = undefined; + var timelineUpdated = false; + var playlist = segmentInfo.playlist; + var currentMediaIndex = segmentInfo.mediaIndex; + + currentMediaIndex += playlist.mediaSequence - this.playlist_.mediaSequence; + segment = playlist.segments[currentMediaIndex]; + + // Update segment meta-data (duration and end-point) based on timeline + if (segment && segmentInfo && segmentInfo.playlist.uri === this.playlist_.uri) { + segmentEnd = _ranges2['default'].findSoleUncommonTimeRangesEnd(segmentInfo.buffered, this.sourceUpdater_.buffered()); + timelineUpdated = updateSegmentMetadata(playlist, currentMediaIndex, segmentEnd); + } + + return timelineUpdated; + } + + /** + * add a number of seconds to the currentTime when determining which + * segment to fetch in order to force the fetcher to advance in cases + * where it may get stuck on the same segment due to buffer gaps or + * missing segment annotation after a rendition switch (especially + * during a live stream) + * + * @private + * @param {Number} secondsToIncrement number of seconds to add to the + * timeCorrection_ variable + * @param {Number} maxSegmentsToWalk maximum number of times we allow this + * function to walk forward + */ + }, { + key: 'incrementTimeCorrection_', + value: function incrementTimeCorrection_(secondsToIncrement, maxSegmentsToWalk) { + // If we have already incremented timeCorrection_ beyond the limit, + // stop searching for a segment and reset timeCorrection_ + if (this.timeCorrection_ >= this.playlist_.targetDuration * maxSegmentsToWalk) { + this.timeCorrection_ = 0; + return false; + } + + this.timeCorrection_ += secondsToIncrement; + return true; + } + }]); + + return SegmentLoader; +})(_videoJs2['default'].EventTarget); + +exports['default'] = SegmentLoader; +module.exports = exports['default']; +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./config":2,"./decrypter":6,"./playlist":10,"./ranges":11,"./source-updater":15}],15:[function(require,module,exports){ +(function (global){ +/** + * @file source-updater.js + */ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); + +var _videoJs2 = _interopRequireDefault(_videoJs); + +/** + * A queue of callbacks to be serialized and applied when a + * MediaSource and its associated SourceBuffers are not in the + * updating state. It is used by the segment loader to update the + * underlying SourceBuffers when new data is loaded, for instance. + * + * @class SourceUpdater + * @param {MediaSource} mediaSource the MediaSource to create the + * SourceBuffer from + * @param {String} mimeType the desired MIME type of the underlying + * SourceBuffer + */ + +var SourceUpdater = (function () { + function SourceUpdater(mediaSource, mimeType) { + var _this = this; + + _classCallCheck(this, SourceUpdater); + + var createSourceBuffer = function createSourceBuffer() { + _this.sourceBuffer_ = mediaSource.addSourceBuffer(mimeType); + + // run completion handlers and process callbacks as updateend + // events fire + _this.onUpdateendCallback_ = function () { + var pendingCallback = _this.pendingCallback_; + + _this.pendingCallback_ = null; + + if (pendingCallback) { + pendingCallback(); + } + + _this.runCallback_(); + }; + + _this.sourceBuffer_.addEventListener('updateend', _this.onUpdateendCallback_); + + _this.runCallback_(); + }; + + this.callbacks_ = []; + this.pendingCallback_ = null; + this.timestampOffset_ = 0; + this.mediaSource = mediaSource; + + if (mediaSource.readyState === 'closed') { + mediaSource.addEventListener('sourceopen', createSourceBuffer); + } else { + createSourceBuffer(); + } + } + + /** + * Aborts the current segment and resets the segment parser. + * + * @param {Function} done function to call when done + * @see http://w3c.github.io/media-source/#widl-SourceBuffer-abort-void + */ + + _createClass(SourceUpdater, [{ + key: 'abort', + value: function abort(done) { + var _this2 = this; + + this.queueCallback_(function () { + _this2.sourceBuffer_.abort(); + }, done); + } + + /** + * Queue an update to append an ArrayBuffer. + * + * @param {ArrayBuffer} bytes + * @param {Function} done the function to call when done + * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data + */ + }, { + key: 'appendBuffer', + value: function appendBuffer(bytes, done) { + var _this3 = this; + + this.queueCallback_(function () { + _this3.sourceBuffer_.appendBuffer(bytes); + }, done); + } + + /** + * Indicates what TimeRanges are buffered in the managed SourceBuffer. + * + * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-buffered + */ + }, { + key: 'buffered', + value: function buffered() { + if (!this.sourceBuffer_) { + return _videoJs2['default'].createTimeRanges(); + } + return this.sourceBuffer_.buffered; + } + + /** + * Queue an update to set the duration. + * + * @param {Double} duration what to set the duration to + * @see http://www.w3.org/TR/media-source/#widl-MediaSource-duration + */ + }, { + key: 'duration', + value: function duration(_duration) { + var _this4 = this; + + this.queueCallback_(function () { + _this4.sourceBuffer_.duration = _duration; + }); + } + + /** + * Queue an update to remove a time range from the buffer. + * + * @param {Number} start where to start the removal + * @param {Number} end where to end the removal + * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end + */ + }, { + key: 'remove', + value: function remove(start, end) { + var _this5 = this; + + this.queueCallback_(function () { + _this5.sourceBuffer_.remove(start, end); + }); + } + + /** + * wether the underlying sourceBuffer is updating or not + * + * @return {Boolean} the updating status of the SourceBuffer + */ + }, { + key: 'updating', + value: function updating() { + return !this.sourceBuffer_ || this.sourceBuffer_.updating; + } + + /** + * Set/get the timestampoffset on the SourceBuffer + * + * @return {Number} the timestamp offset + */ + }, { + key: 'timestampOffset', + value: function timestampOffset(offset) { + var _this6 = this; + + if (typeof offset !== 'undefined') { + this.queueCallback_(function () { + _this6.sourceBuffer_.timestampOffset = offset; + }); + this.timestampOffset_ = offset; + } + return this.timestampOffset_; + } + + /** + * que a callback to run + */ + }, { + key: 'queueCallback_', + value: function queueCallback_(callback, done) { + this.callbacks_.push([callback.bind(this), done]); + this.runCallback_(); + } + + /** + * run a queued callback + */ + }, { + key: 'runCallback_', + value: function runCallback_() { + var callbacks = undefined; + + if (this.sourceBuffer_ && !this.sourceBuffer_.updating && this.callbacks_.length) { + callbacks = this.callbacks_.shift(); + this.pendingCallback_ = callbacks[1]; + callbacks[0](); + } + } + + /** + * dispose of the source updater and the underlying sourceBuffer + */ + }, { + key: 'dispose', + value: function dispose() { + this.sourceBuffer_.removeEventListener('updateend', this.onUpdateendCallback_); + if (this.sourceBuffer_ && this.mediaSource.readyState === 'open') { + this.sourceBuffer_.abort(); + } + } + }]); + + return SourceUpdater; +})(); + +exports['default'] = SourceUpdater; +module.exports = exports['default']; +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],16:[function(require,module,exports){ +/** + * @file stream.js + */ +/** + * A lightweight readable stream implemention that handles event dispatching. + * + * @class Stream + */ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +var Stream = (function () { + function Stream() { + _classCallCheck(this, Stream); + + this.listeners = {}; + } + + /** + * Add a listener for a specified event type. + * + * @param {String} type the event name + * @param {Function} listener the callback to be invoked when an event of + * the specified type occurs + */ + + _createClass(Stream, [{ + key: 'on', + value: function on(type, listener) { + if (!this.listeners[type]) { + this.listeners[type] = []; + } + this.listeners[type].push(listener); + } + + /** + * Remove a listener for a specified event type. + * + * @param {String} type the event name + * @param {Function} listener a function previously registered for this + * type of event through `on` + * @return {Boolean} if we could turn it off or not + */ + }, { + key: 'off', + value: function off(type, listener) { + var index = undefined; + + if (!this.listeners[type]) { + return false; + } + index = this.listeners[type].indexOf(listener); + this.listeners[type].splice(index, 1); + return index > -1; + } + + /** + * Trigger an event of the specified type on this stream. Any additional + * arguments to this function are passed as parameters to event listeners. + * + * @param {String} type the event name + */ + }, { + key: 'trigger', + value: function trigger(type) { + var callbacks = undefined; + var i = undefined; + var length = undefined; + var args = undefined; + + callbacks = this.listeners[type]; + if (!callbacks) { + return; + } + // Slicing the arguments on every invocation of this method + // can add a significant amount of overhead. Avoid the + // intermediate object creation for the common case of a + // single callback argument + if (arguments.length === 2) { + length = callbacks.length; + for (i = 0; i < length; ++i) { + callbacks[i].call(this, arguments[1]); + } + } else { + args = Array.prototype.slice.call(arguments, 1); + length = callbacks.length; + for (i = 0; i < length; ++i) { + callbacks[i].apply(this, args); + } + } + } + + /** + * Destroys the stream and cleans up. + */ + }, { + key: 'dispose', + value: function dispose() { + this.listeners = {}; + } + + /** + * Forwards all `data` events on this stream to the destination stream. The + * destination stream should provide a method `push` to receive the data + * events as they arrive. + * + * @param {Stream} destination the stream that will receive all `data` events + * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options + */ + }, { + key: 'pipe', + value: function pipe(destination) { + this.on('data', function (data) { + destination.push(data); + }); + } + }]); + + return Stream; +})(); + +exports['default'] = Stream; +module.exports = exports['default']; +},{}],17:[function(require,module,exports){ +(function (global){ +/** + * @file xhr.js + */ + +/** + * A wrapper for videojs.xhr that tracks bandwidth. + * + * @param {Object} options options for the XHR + * @param {Function} callback the callback to call when done + * @return {Request} the xhr request that is going to be made + */ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); + +var xhrFactory = function xhrFactory() { + var xhr = function XhrFunction(options, callback) { + // Add a default timeout for all hls requests + options = (0, _videoJs.mergeOptions)({ + timeout: 45e3 + }, options); + + // Allow an optional user-specified function to modify the option + // object before we construct the xhr request + if (XhrFunction.beforeRequest && typeof XhrFunction.beforeRequest === 'function') { + var newOptions = XhrFunction.beforeRequest(options); + + if (newOptions) { + options = newOptions; + } + } + + var request = (0, _videoJs.xhr)(options, function (error, response) { + if (!error && request.response) { + request.responseTime = new Date().getTime(); + request.roundTripTime = request.responseTime - request.requestTime; + request.bytesReceived = request.response.byteLength || request.response.length; + if (!request.bandwidth) { + request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000); + } + } + + // videojs.xhr now uses a specific code + // on the error object to signal that a request has + // timed out errors of setting a boolean on the request object + if (error || request.timedout) { + request.timedout = request.timedout || error.code === 'ETIMEDOUT'; + } else { + request.timedout = false; + } + + // videojs.xhr no longer considers status codes outside of 200 and 0 + // (for file uris) to be errors, but the old XHR did, so emulate that + // behavior. Status 206 may be used in response to byterange requests. + if (!error && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) { + error = new Error('XHR Failed with a response of: ' + (request && (request.response || request.responseText))); + } + + callback(error, request); + }); + + request.requestTime = new Date().getTime(); + return request; + }; + + return xhr; +}; + +exports['default'] = xhrFactory; +module.exports = exports['default']; +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],18:[function(require,module,exports){ + +},{}],19:[function(require,module,exports){ +(function (global){ +var topLevel = typeof global !== 'undefined' ? global : + typeof window !== 'undefined' ? window : {} +var minDoc = require('min-document'); + +if (typeof document !== 'undefined') { + module.exports = document; +} else { + var doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4']; + + if (!doccy) { + doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc; + } + + module.exports = doccy; +} + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"min-document":18}],20:[function(require,module,exports){ +/** Used as the `TypeError` message for "Functions" methods. */ +var FUNC_ERROR_TEXT = 'Expected a function'; + +/* Native method references for those with the same name as other `lodash` methods. */ +var nativeMax = Math.max; + +/** + * Creates a function that invokes `func` with the `this` binding of the + * created function and arguments from `start` and beyond provided as an array. + * + * **Note:** This method is based on the [rest parameter](https://developer.mozilla.org/Web/JavaScript/Reference/Functions/rest_parameters). + * + * @static + * @memberOf _ + * @category Function + * @param {Function} func The function to apply a rest parameter to. + * @param {number} [start=func.length-1] The start position of the rest parameter. + * @returns {Function} Returns the new function. + * @example + * + * var say = _.restParam(function(what, names) { + * return what + ' ' + _.initial(names).join(', ') + + * (_.size(names) > 1 ? ', & ' : '') + _.last(names); + * }); + * + * say('hello', 'fred', 'barney', 'pebbles'); + * // => 'hello fred, barney, & pebbles' + */ +function restParam(func, start) { + if (typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + start = nativeMax(start === undefined ? (func.length - 1) : (+start || 0), 0); + return function() { + var args = arguments, + index = -1, + length = nativeMax(args.length - start, 0), + rest = Array(length); + + while (++index < length) { + rest[index] = args[start + index]; + } + switch (start) { + case 0: return func.call(this, rest); + case 1: return func.call(this, args[0], rest); + case 2: return func.call(this, args[0], args[1], rest); + } + var otherArgs = Array(start + 1); + index = -1; + while (++index < start) { + otherArgs[index] = args[index]; + } + otherArgs[start] = rest; + return func.apply(this, otherArgs); + }; +} + +module.exports = restParam; + +},{}],21:[function(require,module,exports){ +/** + * Copies the values of `source` to `array`. + * + * @private + * @param {Array} source The array to copy values from. + * @param {Array} [array=[]] The array to copy values to. + * @returns {Array} Returns `array`. + */ +function arrayCopy(source, array) { + var index = -1, + length = source.length; + + array || (array = Array(length)); + while (++index < length) { + array[index] = source[index]; + } + return array; +} + +module.exports = arrayCopy; + +},{}],22:[function(require,module,exports){ +/** + * A specialized version of `_.forEach` for arrays without support for callback + * shorthands and `this` binding. + * + * @private + * @param {Array} array The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns `array`. + */ +function arrayEach(array, iteratee) { + var index = -1, + length = array.length; + + while (++index < length) { + if (iteratee(array[index], index, array) === false) { + break; + } + } + return array; +} + +module.exports = arrayEach; + +},{}],23:[function(require,module,exports){ +/** + * Copies properties of `source` to `object`. + * + * @private + * @param {Object} source The object to copy properties from. + * @param {Array} props The property names to copy. + * @param {Object} [object={}] The object to copy properties to. + * @returns {Object} Returns `object`. + */ +function baseCopy(source, props, object) { + object || (object = {}); + + var index = -1, + length = props.length; + + while (++index < length) { + var key = props[index]; + object[key] = source[key]; + } + return object; +} + +module.exports = baseCopy; + +},{}],24:[function(require,module,exports){ +var createBaseFor = require('./createBaseFor'); + +/** + * The base implementation of `baseForIn` and `baseForOwn` which iterates + * over `object` properties returned by `keysFunc` invoking `iteratee` for + * each property. Iteratee functions may exit iteration early by explicitly + * returning `false`. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {Function} keysFunc The function to get the keys of `object`. + * @returns {Object} Returns `object`. + */ +var baseFor = createBaseFor(); + +module.exports = baseFor; + +},{"./createBaseFor":31}],25:[function(require,module,exports){ +var baseFor = require('./baseFor'), + keysIn = require('../object/keysIn'); + +/** + * The base implementation of `_.forIn` without support for callback + * shorthands and `this` binding. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Object} Returns `object`. + */ +function baseForIn(object, iteratee) { + return baseFor(object, iteratee, keysIn); +} + +module.exports = baseForIn; + +},{"../object/keysIn":52,"./baseFor":24}],26:[function(require,module,exports){ +var arrayEach = require('./arrayEach'), + baseMergeDeep = require('./baseMergeDeep'), + isArray = require('../lang/isArray'), + isArrayLike = require('./isArrayLike'), + isObject = require('../lang/isObject'), + isObjectLike = require('./isObjectLike'), + isTypedArray = require('../lang/isTypedArray'), + keys = require('../object/keys'); + +/** + * The base implementation of `_.merge` without support for argument juggling, + * multiple sources, and `this` binding `customizer` functions. + * + * @private + * @param {Object} object The destination object. + * @param {Object} source The source object. + * @param {Function} [customizer] The function to customize merged values. + * @param {Array} [stackA=[]] Tracks traversed source objects. + * @param {Array} [stackB=[]] Associates values with source counterparts. + * @returns {Object} Returns `object`. + */ +function baseMerge(object, source, customizer, stackA, stackB) { + if (!isObject(object)) { + return object; + } + var isSrcArr = isArrayLike(source) && (isArray(source) || isTypedArray(source)), + props = isSrcArr ? undefined : keys(source); + + arrayEach(props || source, function(srcValue, key) { + if (props) { + key = srcValue; + srcValue = source[key]; + } + if (isObjectLike(srcValue)) { + stackA || (stackA = []); + stackB || (stackB = []); + baseMergeDeep(object, source, key, baseMerge, customizer, stackA, stackB); + } + else { + var value = object[key], + result = customizer ? customizer(value, srcValue, key, object, source) : undefined, + isCommon = result === undefined; + + if (isCommon) { + result = srcValue; + } + if ((result !== undefined || (isSrcArr && !(key in object))) && + (isCommon || (result === result ? (result !== value) : (value === value)))) { + object[key] = result; + } + } + }); + return object; +} + +module.exports = baseMerge; + +},{"../lang/isArray":43,"../lang/isObject":46,"../lang/isTypedArray":49,"../object/keys":51,"./arrayEach":22,"./baseMergeDeep":27,"./isArrayLike":34,"./isObjectLike":39}],27:[function(require,module,exports){ +var arrayCopy = require('./arrayCopy'), + isArguments = require('../lang/isArguments'), + isArray = require('../lang/isArray'), + isArrayLike = require('./isArrayLike'), + isPlainObject = require('../lang/isPlainObject'), + isTypedArray = require('../lang/isTypedArray'), + toPlainObject = require('../lang/toPlainObject'); + +/** + * A specialized version of `baseMerge` for arrays and objects which performs + * deep merges and tracks traversed objects enabling objects with circular + * references to be merged. + * + * @private + * @param {Object} object The destination object. + * @param {Object} source The source object. + * @param {string} key The key of the value to merge. + * @param {Function} mergeFunc The function to merge values. + * @param {Function} [customizer] The function to customize merged values. + * @param {Array} [stackA=[]] Tracks traversed source objects. + * @param {Array} [stackB=[]] Associates values with source counterparts. + * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + */ +function baseMergeDeep(object, source, key, mergeFunc, customizer, stackA, stackB) { + var length = stackA.length, + srcValue = source[key]; + + while (length--) { + if (stackA[length] == srcValue) { + object[key] = stackB[length]; + return; + } + } + var value = object[key], + result = customizer ? customizer(value, srcValue, key, object, source) : undefined, + isCommon = result === undefined; + + if (isCommon) { + result = srcValue; + if (isArrayLike(srcValue) && (isArray(srcValue) || isTypedArray(srcValue))) { + result = isArray(value) + ? value + : (isArrayLike(value) ? arrayCopy(value) : []); + } + else if (isPlainObject(srcValue) || isArguments(srcValue)) { + result = isArguments(value) + ? toPlainObject(value) + : (isPlainObject(value) ? value : {}); + } + else { + isCommon = false; + } + } + // Add the source value to the stack of traversed objects and associate + // it with its merged value. + stackA.push(srcValue); + stackB.push(result); + + if (isCommon) { + // Recursively merge objects and arrays (susceptible to call stack limits). + object[key] = mergeFunc(result, srcValue, customizer, stackA, stackB); + } else if (result === result ? (result !== value) : (value === value)) { + object[key] = result; + } +} + +module.exports = baseMergeDeep; + +},{"../lang/isArguments":42,"../lang/isArray":43,"../lang/isPlainObject":47,"../lang/isTypedArray":49,"../lang/toPlainObject":50,"./arrayCopy":21,"./isArrayLike":34}],28:[function(require,module,exports){ +var toObject = require('./toObject'); + +/** + * The base implementation of `_.property` without support for deep paths. + * + * @private + * @param {string} key The key of the property to get. + * @returns {Function} Returns the new function. + */ +function baseProperty(key) { + return function(object) { + return object == null ? undefined : toObject(object)[key]; + }; +} + +module.exports = baseProperty; + +},{"./toObject":41}],29:[function(require,module,exports){ +var identity = require('../utility/identity'); + +/** + * A specialized version of `baseCallback` which only supports `this` binding + * and specifying the number of arguments to provide to `func`. + * + * @private + * @param {Function} func The function to bind. + * @param {*} thisArg The `this` binding of `func`. + * @param {number} [argCount] The number of arguments to provide to `func`. + * @returns {Function} Returns the callback. + */ +function bindCallback(func, thisArg, argCount) { + if (typeof func != 'function') { + return identity; + } + if (thisArg === undefined) { + return func; + } + switch (argCount) { + case 1: return function(value) { + return func.call(thisArg, value); + }; + case 3: return function(value, index, collection) { + return func.call(thisArg, value, index, collection); + }; + case 4: return function(accumulator, value, index, collection) { + return func.call(thisArg, accumulator, value, index, collection); + }; + case 5: return function(value, other, key, object, source) { + return func.call(thisArg, value, other, key, object, source); + }; + } + return function() { + return func.apply(thisArg, arguments); + }; +} + +module.exports = bindCallback; + +},{"../utility/identity":55}],30:[function(require,module,exports){ +var bindCallback = require('./bindCallback'), + isIterateeCall = require('./isIterateeCall'), + restParam = require('../function/restParam'); + +/** + * Creates a `_.assign`, `_.defaults`, or `_.merge` function. + * + * @private + * @param {Function} assigner The function to assign values. + * @returns {Function} Returns the new assigner function. + */ +function createAssigner(assigner) { + return restParam(function(object, sources) { + var index = -1, + length = object == null ? 0 : sources.length, + customizer = length > 2 ? sources[length - 2] : undefined, + guard = length > 2 ? sources[2] : undefined, + thisArg = length > 1 ? sources[length - 1] : undefined; + + if (typeof customizer == 'function') { + customizer = bindCallback(customizer, thisArg, 5); + length -= 2; + } else { + customizer = typeof thisArg == 'function' ? thisArg : undefined; + length -= (customizer ? 1 : 0); + } + if (guard && isIterateeCall(sources[0], sources[1], guard)) { + customizer = length < 3 ? undefined : customizer; + length = 1; + } + while (++index < length) { + var source = sources[index]; + if (source) { + assigner(object, source, customizer); + } + } + return object; + }); +} + +module.exports = createAssigner; + +},{"../function/restParam":20,"./bindCallback":29,"./isIterateeCall":37}],31:[function(require,module,exports){ +var toObject = require('./toObject'); + +/** + * Creates a base function for `_.forIn` or `_.forInRight`. + * + * @private + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new base function. + */ +function createBaseFor(fromRight) { + return function(object, iteratee, keysFunc) { + var iterable = toObject(object), + props = keysFunc(object), + length = props.length, + index = fromRight ? length : -1; + + while ((fromRight ? index-- : ++index < length)) { + var key = props[index]; + if (iteratee(iterable[key], key, iterable) === false) { + break; + } + } + return object; + }; +} + +module.exports = createBaseFor; + +},{"./toObject":41}],32:[function(require,module,exports){ +var baseProperty = require('./baseProperty'); + +/** + * Gets the "length" property value of `object`. + * + * **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792) + * that affects Safari on at least iOS 8.1-8.3 ARM64. + * + * @private + * @param {Object} object The object to query. + * @returns {*} Returns the "length" value. + */ +var getLength = baseProperty('length'); + +module.exports = getLength; + +},{"./baseProperty":28}],33:[function(require,module,exports){ +var isNative = require('../lang/isNative'); + +/** + * Gets the native function at `key` of `object`. + * + * @private + * @param {Object} object The object to query. + * @param {string} key The key of the method to get. + * @returns {*} Returns the function if it's native, else `undefined`. + */ +function getNative(object, key) { + var value = object == null ? undefined : object[key]; + return isNative(value) ? value : undefined; +} + +module.exports = getNative; + +},{"../lang/isNative":45}],34:[function(require,module,exports){ +var getLength = require('./getLength'), + isLength = require('./isLength'); + +/** + * Checks if `value` is array-like. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is array-like, else `false`. + */ +function isArrayLike(value) { + return value != null && isLength(getLength(value)); +} + +module.exports = isArrayLike; + +},{"./getLength":32,"./isLength":38}],35:[function(require,module,exports){ +/** + * Checks if `value` is a host object in IE < 9. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a host object, else `false`. + */ +var isHostObject = (function() { + try { + Object({ 'toString': 0 } + ''); + } catch(e) { + return function() { return false; }; + } + return function(value) { + // IE < 9 presents many host objects as `Object` objects that can coerce + // to strings despite having improperly defined `toString` methods. + return typeof value.toString != 'function' && typeof (value + '') == 'string'; + }; +}()); + +module.exports = isHostObject; + +},{}],36:[function(require,module,exports){ +/** Used to detect unsigned integer values. */ +var reIsUint = /^\d+$/; + +/** + * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer) + * of an array-like value. + */ +var MAX_SAFE_INTEGER = 9007199254740991; + +/** + * Checks if `value` is a valid array-like index. + * + * @private + * @param {*} value The value to check. + * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. + * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. + */ +function isIndex(value, length) { + value = (typeof value == 'number' || reIsUint.test(value)) ? +value : -1; + length = length == null ? MAX_SAFE_INTEGER : length; + return value > -1 && value % 1 == 0 && value < length; +} + +module.exports = isIndex; + +},{}],37:[function(require,module,exports){ +var isArrayLike = require('./isArrayLike'), + isIndex = require('./isIndex'), + isObject = require('../lang/isObject'); + +/** + * Checks if the provided arguments are from an iteratee call. + * + * @private + * @param {*} value The potential iteratee value argument. + * @param {*} index The potential iteratee index or key argument. + * @param {*} object The potential iteratee object argument. + * @returns {boolean} Returns `true` if the arguments are from an iteratee call, else `false`. + */ +function isIterateeCall(value, index, object) { + if (!isObject(object)) { + return false; + } + var type = typeof index; + if (type == 'number' + ? (isArrayLike(object) && isIndex(index, object.length)) + : (type == 'string' && index in object)) { + var other = object[index]; + return value === value ? (value === other) : (other !== other); + } + return false; +} + +module.exports = isIterateeCall; + +},{"../lang/isObject":46,"./isArrayLike":34,"./isIndex":36}],38:[function(require,module,exports){ +/** + * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer) + * of an array-like value. + */ +var MAX_SAFE_INTEGER = 9007199254740991; + +/** + * Checks if `value` is a valid array-like length. + * + * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength). + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. + */ +function isLength(value) { + return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; +} + +module.exports = isLength; + +},{}],39:[function(require,module,exports){ +/** + * Checks if `value` is object-like. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. + */ +function isObjectLike(value) { + return !!value && typeof value == 'object'; +} + +module.exports = isObjectLike; + +},{}],40:[function(require,module,exports){ +var isArguments = require('../lang/isArguments'), + isArray = require('../lang/isArray'), + isIndex = require('./isIndex'), + isLength = require('./isLength'), + isString = require('../lang/isString'), + keysIn = require('../object/keysIn'); + +/** Used for native method references. */ +var objectProto = Object.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty = objectProto.hasOwnProperty; + +/** + * A fallback implementation of `Object.keys` which creates an array of the + * own enumerable property names of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + */ +function shimKeys(object) { + var props = keysIn(object), + propsLength = props.length, + length = propsLength && object.length; + + var allowIndexes = !!length && isLength(length) && + (isArray(object) || isArguments(object) || isString(object)); + + var index = -1, + result = []; + + while (++index < propsLength) { + var key = props[index]; + if ((allowIndexes && isIndex(key, length)) || hasOwnProperty.call(object, key)) { + result.push(key); + } + } + return result; +} + +module.exports = shimKeys; + +},{"../lang/isArguments":42,"../lang/isArray":43,"../lang/isString":48,"../object/keysIn":52,"./isIndex":36,"./isLength":38}],41:[function(require,module,exports){ +var isObject = require('../lang/isObject'), + isString = require('../lang/isString'), + support = require('../support'); + +/** + * Converts `value` to an object if it's not one. + * + * @private + * @param {*} value The value to process. + * @returns {Object} Returns the object. + */ +function toObject(value) { + if (support.unindexedChars && isString(value)) { + var index = -1, + length = value.length, + result = Object(value); + + while (++index < length) { + result[index] = value.charAt(index); + } + return result; + } + return isObject(value) ? value : Object(value); +} + +module.exports = toObject; + +},{"../lang/isObject":46,"../lang/isString":48,"../support":54}],42:[function(require,module,exports){ +var isArrayLike = require('../internal/isArrayLike'), + isObjectLike = require('../internal/isObjectLike'); + +/** Used for native method references. */ +var objectProto = Object.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty = objectProto.hasOwnProperty; + +/** Native method references. */ +var propertyIsEnumerable = objectProto.propertyIsEnumerable; + +/** + * Checks if `value` is classified as an `arguments` object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isArguments(function() { return arguments; }()); + * // => true + * + * _.isArguments([1, 2, 3]); + * // => false + */ +function isArguments(value) { + return isObjectLike(value) && isArrayLike(value) && + hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee'); +} + +module.exports = isArguments; + +},{"../internal/isArrayLike":34,"../internal/isObjectLike":39}],43:[function(require,module,exports){ +var getNative = require('../internal/getNative'), + isLength = require('../internal/isLength'), + isObjectLike = require('../internal/isObjectLike'); + +/** `Object#toString` result references. */ +var arrayTag = '[object Array]'; + +/** Used for native method references. */ +var objectProto = Object.prototype; + +/** + * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) + * of values. + */ +var objToString = objectProto.toString; + +/* Native method references for those with the same name as other `lodash` methods. */ +var nativeIsArray = getNative(Array, 'isArray'); + +/** + * Checks if `value` is classified as an `Array` object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isArray([1, 2, 3]); + * // => true + * + * _.isArray(function() { return arguments; }()); + * // => false + */ +var isArray = nativeIsArray || function(value) { + return isObjectLike(value) && isLength(value.length) && objToString.call(value) == arrayTag; +}; + +module.exports = isArray; + +},{"../internal/getNative":33,"../internal/isLength":38,"../internal/isObjectLike":39}],44:[function(require,module,exports){ +var isObject = require('./isObject'); + +/** `Object#toString` result references. */ +var funcTag = '[object Function]'; + +/** Used for native method references. */ +var objectProto = Object.prototype; + +/** + * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) + * of values. + */ +var objToString = objectProto.toString; + +/** + * Checks if `value` is classified as a `Function` object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isFunction(_); + * // => true + * + * _.isFunction(/abc/); + * // => false + */ +function isFunction(value) { + // The use of `Object#toString` avoids issues with the `typeof` operator + // in older versions of Chrome and Safari which return 'function' for regexes + // and Safari 8 which returns 'object' for typed array constructors. + return isObject(value) && objToString.call(value) == funcTag; +} + +module.exports = isFunction; + +},{"./isObject":46}],45:[function(require,module,exports){ +var isFunction = require('./isFunction'), + isHostObject = require('../internal/isHostObject'), + isObjectLike = require('../internal/isObjectLike'); + +/** Used to detect host constructors (Safari > 5). */ +var reIsHostCtor = /^\[object .+?Constructor\]$/; + +/** Used for native method references. */ +var objectProto = Object.prototype; + +/** Used to resolve the decompiled source of functions. */ +var fnToString = Function.prototype.toString; + +/** Used to check objects for own properties. */ +var hasOwnProperty = objectProto.hasOwnProperty; + +/** Used to detect if a method is native. */ +var reIsNative = RegExp('^' + + fnToString.call(hasOwnProperty).replace(/[\\^$.*+?()[\]{}|]/g, '\\$&') + .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' +); + +/** + * Checks if `value` is a native function. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a native function, else `false`. + * @example + * + * _.isNative(Array.prototype.push); + * // => true + * + * _.isNative(_); + * // => false + */ +function isNative(value) { + if (value == null) { + return false; + } + if (isFunction(value)) { + return reIsNative.test(fnToString.call(value)); + } + return isObjectLike(value) && (isHostObject(value) ? reIsNative : reIsHostCtor).test(value); +} + +module.exports = isNative; + +},{"../internal/isHostObject":35,"../internal/isObjectLike":39,"./isFunction":44}],46:[function(require,module,exports){ +/** + * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. + * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(1); + * // => false + */ +function isObject(value) { + // Avoid a V8 JIT bug in Chrome 19-20. + // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. + var type = typeof value; + return !!value && (type == 'object' || type == 'function'); +} + +module.exports = isObject; + +},{}],47:[function(require,module,exports){ +var baseForIn = require('../internal/baseForIn'), + isArguments = require('./isArguments'), + isHostObject = require('../internal/isHostObject'), + isObjectLike = require('../internal/isObjectLike'), + support = require('../support'); + +/** `Object#toString` result references. */ +var objectTag = '[object Object]'; + +/** Used for native method references. */ +var objectProto = Object.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty = objectProto.hasOwnProperty; + +/** + * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) + * of values. + */ +var objToString = objectProto.toString; + +/** + * Checks if `value` is a plain object, that is, an object created by the + * `Object` constructor or one with a `[[Prototype]]` of `null`. + * + * **Note:** This method assumes objects created by the `Object` constructor + * have no inherited enumerable properties. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a plain object, else `false`. + * @example + * + * function Foo() { + * this.a = 1; + * } + * + * _.isPlainObject(new Foo); + * // => false + * + * _.isPlainObject([1, 2, 3]); + * // => false + * + * _.isPlainObject({ 'x': 0, 'y': 0 }); + * // => true + * + * _.isPlainObject(Object.create(null)); + * // => true + */ +function isPlainObject(value) { + var Ctor; + + // Exit early for non `Object` objects. + if (!(isObjectLike(value) && objToString.call(value) == objectTag && !isHostObject(value) && !isArguments(value)) || + (!hasOwnProperty.call(value, 'constructor') && (Ctor = value.constructor, typeof Ctor == 'function' && !(Ctor instanceof Ctor)))) { + return false; + } + // IE < 9 iterates inherited properties before own properties. If the first + // iterated property is an object's own property then there are no inherited + // enumerable properties. + var result; + if (support.ownLast) { + baseForIn(value, function(subValue, key, object) { + result = hasOwnProperty.call(object, key); + return false; + }); + return result !== false; + } + // In most environments an object's own properties are iterated before + // its inherited properties. If the last iterated property is an object's + // own property then there are no inherited enumerable properties. + baseForIn(value, function(subValue, key) { + result = key; + }); + return result === undefined || hasOwnProperty.call(value, result); +} + +module.exports = isPlainObject; + +},{"../internal/baseForIn":25,"../internal/isHostObject":35,"../internal/isObjectLike":39,"../support":54,"./isArguments":42}],48:[function(require,module,exports){ +var isObjectLike = require('../internal/isObjectLike'); + +/** `Object#toString` result references. */ +var stringTag = '[object String]'; + +/** Used for native method references. */ +var objectProto = Object.prototype; + +/** + * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) + * of values. + */ +var objToString = objectProto.toString; + +/** + * Checks if `value` is classified as a `String` primitive or object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isString('abc'); + * // => true + * + * _.isString(1); + * // => false + */ +function isString(value) { + return typeof value == 'string' || (isObjectLike(value) && objToString.call(value) == stringTag); +} + +module.exports = isString; + +},{"../internal/isObjectLike":39}],49:[function(require,module,exports){ +var isLength = require('../internal/isLength'), + isObjectLike = require('../internal/isObjectLike'); + +/** `Object#toString` result references. */ +var argsTag = '[object Arguments]', + arrayTag = '[object Array]', + boolTag = '[object Boolean]', + dateTag = '[object Date]', + errorTag = '[object Error]', + funcTag = '[object Function]', + mapTag = '[object Map]', + numberTag = '[object Number]', + objectTag = '[object Object]', + regexpTag = '[object RegExp]', + setTag = '[object Set]', + stringTag = '[object String]', + weakMapTag = '[object WeakMap]'; + +var arrayBufferTag = '[object ArrayBuffer]', + float32Tag = '[object Float32Array]', + float64Tag = '[object Float64Array]', + int8Tag = '[object Int8Array]', + int16Tag = '[object Int16Array]', + int32Tag = '[object Int32Array]', + uint8Tag = '[object Uint8Array]', + uint8ClampedTag = '[object Uint8ClampedArray]', + uint16Tag = '[object Uint16Array]', + uint32Tag = '[object Uint32Array]'; + +/** Used to identify `toStringTag` values of typed arrays. */ +var typedArrayTags = {}; +typedArrayTags[float32Tag] = typedArrayTags[float64Tag] = +typedArrayTags[int8Tag] = typedArrayTags[int16Tag] = +typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] = +typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] = +typedArrayTags[uint32Tag] = true; +typedArrayTags[argsTag] = typedArrayTags[arrayTag] = +typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] = +typedArrayTags[dateTag] = typedArrayTags[errorTag] = +typedArrayTags[funcTag] = typedArrayTags[mapTag] = +typedArrayTags[numberTag] = typedArrayTags[objectTag] = +typedArrayTags[regexpTag] = typedArrayTags[setTag] = +typedArrayTags[stringTag] = typedArrayTags[weakMapTag] = false; + +/** Used for native method references. */ +var objectProto = Object.prototype; + +/** + * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) + * of values. + */ +var objToString = objectProto.toString; + +/** + * Checks if `value` is classified as a typed array. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isTypedArray(new Uint8Array); + * // => true + * + * _.isTypedArray([]); + * // => false + */ +function isTypedArray(value) { + return isObjectLike(value) && isLength(value.length) && !!typedArrayTags[objToString.call(value)]; +} + +module.exports = isTypedArray; + +},{"../internal/isLength":38,"../internal/isObjectLike":39}],50:[function(require,module,exports){ +var baseCopy = require('../internal/baseCopy'), + keysIn = require('../object/keysIn'); + +/** + * Converts `value` to a plain object flattening inherited enumerable + * properties of `value` to own properties of the plain object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to convert. + * @returns {Object} Returns the converted plain object. + * @example + * + * function Foo() { + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.assign({ 'a': 1 }, new Foo); + * // => { 'a': 1, 'b': 2 } + * + * _.assign({ 'a': 1 }, _.toPlainObject(new Foo)); + * // => { 'a': 1, 'b': 2, 'c': 3 } + */ +function toPlainObject(value) { + return baseCopy(value, keysIn(value)); +} + +module.exports = toPlainObject; + +},{"../internal/baseCopy":23,"../object/keysIn":52}],51:[function(require,module,exports){ +var getNative = require('../internal/getNative'), + isArrayLike = require('../internal/isArrayLike'), + isObject = require('../lang/isObject'), + shimKeys = require('../internal/shimKeys'), + support = require('../support'); + +/* Native method references for those with the same name as other `lodash` methods. */ +var nativeKeys = getNative(Object, 'keys'); + +/** + * Creates an array of the own enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. See the + * [ES spec](http://ecma-international.org/ecma-262/6.0/#sec-object.keys) + * for more details. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keys(new Foo); + * // => ['a', 'b'] (iteration order is not guaranteed) + * + * _.keys('hi'); + * // => ['0', '1'] + */ +var keys = !nativeKeys ? shimKeys : function(object) { + var Ctor = object == null ? undefined : object.constructor; + if ((typeof Ctor == 'function' && Ctor.prototype === object) || + (typeof object == 'function' ? support.enumPrototypes : isArrayLike(object))) { + return shimKeys(object); + } + return isObject(object) ? nativeKeys(object) : []; +}; + +module.exports = keys; + +},{"../internal/getNative":33,"../internal/isArrayLike":34,"../internal/shimKeys":40,"../lang/isObject":46,"../support":54}],52:[function(require,module,exports){ +var arrayEach = require('../internal/arrayEach'), + isArguments = require('../lang/isArguments'), + isArray = require('../lang/isArray'), + isFunction = require('../lang/isFunction'), + isIndex = require('../internal/isIndex'), + isLength = require('../internal/isLength'), + isObject = require('../lang/isObject'), + isString = require('../lang/isString'), + support = require('../support'); + +/** `Object#toString` result references. */ +var arrayTag = '[object Array]', + boolTag = '[object Boolean]', + dateTag = '[object Date]', + errorTag = '[object Error]', + funcTag = '[object Function]', + numberTag = '[object Number]', + objectTag = '[object Object]', + regexpTag = '[object RegExp]', + stringTag = '[object String]'; + +/** Used to fix the JScript `[[DontEnum]]` bug. */ +var shadowProps = [ + 'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', + 'toLocaleString', 'toString', 'valueOf' +]; + +/** Used for native method references. */ +var errorProto = Error.prototype, + objectProto = Object.prototype, + stringProto = String.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty = objectProto.hasOwnProperty; + +/** + * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) + * of values. + */ +var objToString = objectProto.toString; + +/** Used to avoid iterating over non-enumerable properties in IE < 9. */ +var nonEnumProps = {}; +nonEnumProps[arrayTag] = nonEnumProps[dateTag] = nonEnumProps[numberTag] = { 'constructor': true, 'toLocaleString': true, 'toString': true, 'valueOf': true }; +nonEnumProps[boolTag] = nonEnumProps[stringTag] = { 'constructor': true, 'toString': true, 'valueOf': true }; +nonEnumProps[errorTag] = nonEnumProps[funcTag] = nonEnumProps[regexpTag] = { 'constructor': true, 'toString': true }; +nonEnumProps[objectTag] = { 'constructor': true }; + +arrayEach(shadowProps, function(key) { + for (var tag in nonEnumProps) { + if (hasOwnProperty.call(nonEnumProps, tag)) { + var props = nonEnumProps[tag]; + props[key] = hasOwnProperty.call(props, key); + } + } +}); + +/** + * Creates an array of the own and inherited enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keysIn(new Foo); + * // => ['a', 'b', 'c'] (iteration order is not guaranteed) + */ +function keysIn(object) { + if (object == null) { + return []; + } + if (!isObject(object)) { + object = Object(object); + } + var length = object.length; + + length = (length && isLength(length) && + (isArray(object) || isArguments(object) || isString(object)) && length) || 0; + + var Ctor = object.constructor, + index = -1, + proto = (isFunction(Ctor) && Ctor.prototype) || objectProto, + isProto = proto === object, + result = Array(length), + skipIndexes = length > 0, + skipErrorProps = support.enumErrorProps && (object === errorProto || object instanceof Error), + skipProto = support.enumPrototypes && isFunction(object); + + while (++index < length) { + result[index] = (index + ''); + } + // lodash skips the `constructor` property when it infers it's iterating + // over a `prototype` object because IE < 9 can't set the `[[Enumerable]]` + // attribute of an existing property and the `constructor` property of a + // prototype defaults to non-enumerable. + for (var key in object) { + if (!(skipProto && key == 'prototype') && + !(skipErrorProps && (key == 'message' || key == 'name')) && + !(skipIndexes && isIndex(key, length)) && + !(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) { + result.push(key); + } + } + if (support.nonEnumShadows && object !== objectProto) { + var tag = object === stringProto ? stringTag : (object === errorProto ? errorTag : objToString.call(object)), + nonEnums = nonEnumProps[tag] || nonEnumProps[objectTag]; + + if (tag == objectTag) { + proto = objectProto; + } + length = shadowProps.length; + while (length--) { + key = shadowProps[length]; + var nonEnum = nonEnums[key]; + if (!(isProto && nonEnum) && + (nonEnum ? hasOwnProperty.call(object, key) : object[key] !== proto[key])) { + result.push(key); + } + } + } + return result; +} + +module.exports = keysIn; + +},{"../internal/arrayEach":22,"../internal/isIndex":36,"../internal/isLength":38,"../lang/isArguments":42,"../lang/isArray":43,"../lang/isFunction":44,"../lang/isObject":46,"../lang/isString":48,"../support":54}],53:[function(require,module,exports){ +var baseMerge = require('../internal/baseMerge'), + createAssigner = require('../internal/createAssigner'); + +/** + * Recursively merges own enumerable properties of the source object(s), that + * don't resolve to `undefined` into the destination object. Subsequent sources + * overwrite property assignments of previous sources. If `customizer` is + * provided it's invoked to produce the merged values of the destination and + * source properties. If `customizer` returns `undefined` merging is handled + * by the method instead. The `customizer` is bound to `thisArg` and invoked + * with five arguments: (objectValue, sourceValue, key, object, source). + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @param {Function} [customizer] The function to customize assigned values. + * @param {*} [thisArg] The `this` binding of `customizer`. + * @returns {Object} Returns `object`. + * @example + * + * var users = { + * 'data': [{ 'user': 'barney' }, { 'user': 'fred' }] + * }; + * + * var ages = { + * 'data': [{ 'age': 36 }, { 'age': 40 }] + * }; + * + * _.merge(users, ages); + * // => { 'data': [{ 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 }] } + * + * // using a customizer callback + * var object = { + * 'fruits': ['apple'], + * 'vegetables': ['beet'] + * }; + * + * var other = { + * 'fruits': ['banana'], + * 'vegetables': ['carrot'] + * }; + * + * _.merge(object, other, function(a, b) { + * if (_.isArray(a)) { + * return a.concat(b); + * } + * }); + * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot'] } + */ +var merge = createAssigner(baseMerge); + +module.exports = merge; + +},{"../internal/baseMerge":26,"../internal/createAssigner":30}],54:[function(require,module,exports){ +/** Used for native method references. */ +var arrayProto = Array.prototype, + errorProto = Error.prototype, + objectProto = Object.prototype; + +/** Native method references. */ +var propertyIsEnumerable = objectProto.propertyIsEnumerable, + splice = arrayProto.splice; + +/** + * An object environment feature flags. + * + * @static + * @memberOf _ + * @type Object + */ +var support = {}; + +(function(x) { + var Ctor = function() { this.x = x; }, + object = { '0': x, 'length': x }, + props = []; + + Ctor.prototype = { 'valueOf': x, 'y': x }; + for (var key in new Ctor) { props.push(key); } + + /** + * Detect if `name` or `message` properties of `Error.prototype` are + * enumerable by default (IE < 9, Safari < 5.1). + * + * @memberOf _.support + * @type boolean + */ + support.enumErrorProps = propertyIsEnumerable.call(errorProto, 'message') || + propertyIsEnumerable.call(errorProto, 'name'); + + /** + * Detect if `prototype` properties are enumerable by default. + * + * Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1 + * (if the prototype or a property on the prototype has been set) + * incorrectly set the `[[Enumerable]]` value of a function's `prototype` + * property to `true`. + * + * @memberOf _.support + * @type boolean + */ + support.enumPrototypes = propertyIsEnumerable.call(Ctor, 'prototype'); + + /** + * Detect if properties shadowing those on `Object.prototype` are non-enumerable. + * + * In IE < 9 an object's own properties, shadowing non-enumerable ones, + * are made non-enumerable as well (a.k.a the JScript `[[DontEnum]]` bug). + * + * @memberOf _.support + * @type boolean + */ + support.nonEnumShadows = !/valueOf/.test(props); + + /** + * Detect if own properties are iterated after inherited properties (IE < 9). + * + * @memberOf _.support + * @type boolean + */ + support.ownLast = props[0] != 'x'; + + /** + * Detect if `Array#shift` and `Array#splice` augment array-like objects + * correctly. + * + * Firefox < 10, compatibility modes of IE 8, and IE < 9 have buggy Array + * `shift()` and `splice()` functions that fail to remove the last element, + * `value[0]`, of array-like objects even though the "length" property is + * set to `0`. The `shift()` method is buggy in compatibility modes of IE 8, + * while `splice()` is buggy regardless of mode in IE < 9. + * + * @memberOf _.support + * @type boolean + */ + support.spliceObjects = (splice.call(object, 0, 1), !object[0]); + + /** + * Detect lack of support for accessing string characters by index. + * + * IE < 8 can't access characters by index. IE 8 can only access characters + * by index on string literals, not string objects. + * + * @memberOf _.support + * @type boolean + */ + support.unindexedChars = ('x'[0] + Object('x')[0]) != 'xx'; +}(1, 0)); + +module.exports = support; + +},{}],55:[function(require,module,exports){ +/** + * This method returns the first argument provided to it. + * + * @static + * @memberOf _ + * @category Utility + * @param {*} value Any value. + * @returns {*} Returns `value`. + * @example + * + * var object = { 'user': 'fred' }; + * + * _.identity(object) === object; + * // => true + */ +function identity(value) { + return value; +} + +module.exports = identity; + +},{}],56:[function(require,module,exports){ +/** + * @file m3u8/index.js + * + * Utilities for parsing M3U8 files. If the entire manifest is available, + * `Parser` will create an object representation with enough detail for managing + * playback. `ParseStream` and `LineStream` are lower-level parsing primitives + * that do not assume the entirety of the manifest is ready and expose a + * ReadableStream-like interface. + */ + +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _lineStream = require('./line-stream'); + +var _lineStream2 = _interopRequireDefault(_lineStream); + +var _parseStream = require('./parse-stream'); + +var _parseStream2 = _interopRequireDefault(_parseStream); + +var _parser = require('./parser'); + +var _parser2 = _interopRequireDefault(_parser); + +exports['default'] = { + LineStream: _lineStream2['default'], + ParseStream: _parseStream2['default'], + Parser: _parser2['default'] +}; +module.exports = exports['default']; +},{"./line-stream":57,"./parse-stream":58,"./parser":59}],57:[function(require,module,exports){ +/** + * @file m3u8/line-stream.js + */ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _stream = require('./stream'); + +var _stream2 = _interopRequireDefault(_stream); + +/** + * A stream that buffers string input and generates a `data` event for each + * line. + * + * @class LineStream + * @extends Stream + */ + +var LineStream = (function (_Stream) { + _inherits(LineStream, _Stream); + + function LineStream() { + _classCallCheck(this, LineStream); + + _get(Object.getPrototypeOf(LineStream.prototype), 'constructor', this).call(this); + this.buffer = ''; + } + + /** + * Add new data to be parsed. + * + * @param {String} data the text to process + */ + + _createClass(LineStream, [{ + key: 'push', + value: function push(data) { + var nextNewline = undefined; + + this.buffer += data; + nextNewline = this.buffer.indexOf('\n'); + + for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) { + this.trigger('data', this.buffer.substring(0, nextNewline)); + this.buffer = this.buffer.substring(nextNewline + 1); + } + } + }]); + + return LineStream; +})(_stream2['default']); + +exports['default'] = LineStream; +module.exports = exports['default']; +},{"./stream":60}],58:[function(require,module,exports){ +/** + * @file m3u8/parse-stream.js + */ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _stream = require('./stream'); + +var _stream2 = _interopRequireDefault(_stream); + +/** + * "forgiving" attribute list psuedo-grammar: + * attributes -> keyvalue (',' keyvalue)* + * keyvalue -> key '=' value + * key -> [^=]* + * value -> '"' [^"]* '"' | [^,]* + */ +var attributeSeparator = function attributeSeparator() { + var key = '[^=]*'; + var value = '"[^"]*"|[^,]*'; + var keyvalue = '(?:' + key + ')=(?:' + value + ')'; + + return new RegExp('(?:^|,)(' + keyvalue + ')'); +}; + +/** + * Parse attributes from a line given the seperator + * + * @param {String} attributes the attibute line to parse + */ +var parseAttributes = function parseAttributes(attributes) { + // split the string using attributes as the separator + var attrs = attributes.split(attributeSeparator()); + var i = attrs.length; + var result = {}; + var attr = undefined; + + while (i--) { + // filter out unmatched portions of the string + if (attrs[i] === '') { + continue; + } + + // split the key and value + attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); + // trim whitespace and remove optional quotes around the value + attr[0] = attr[0].replace(/^\s+|\s+$/g, ''); + attr[1] = attr[1].replace(/^\s+|\s+$/g, ''); + attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1'); + result[attr[0]] = attr[1]; + } + return result; +}; + +/** + * A line-level M3U8 parser event stream. It expects to receive input one + * line at a time and performs a context-free parse of its contents. A stream + * interpretation of a manifest can be useful if the manifest is expected to + * be too large to fit comfortably into memory or the entirety of the input + * is not immediately available. Otherwise, it's probably much easier to work + * with a regular `Parser` object. + * + * Produces `data` events with an object that captures the parser's + * interpretation of the input. That object has a property `tag` that is one + * of `uri`, `comment`, or `tag`. URIs only have a single additional + * property, `line`, which captures the entirety of the input without + * interpretation. Comments similarly have a single additional property + * `text` which is the input without the leading `#`. + * + * Tags always have a property `tagType` which is the lower-cased version of + * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance, + * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized + * tags are given the tag type `unknown` and a single additional property + * `data` with the remainder of the input. + * + * @class ParseStream + * @extends Stream + */ + +var ParseStream = (function (_Stream) { + _inherits(ParseStream, _Stream); + + function ParseStream() { + _classCallCheck(this, ParseStream); + + _get(Object.getPrototypeOf(ParseStream.prototype), 'constructor', this).call(this); + } + + /** + * Parses an additional line of input. + * + * @param {String} line a single line of an M3U8 file to parse + */ + + _createClass(ParseStream, [{ + key: 'push', + value: function push(line) { + var match = undefined; + var event = undefined; + + // strip whitespace + line = line.replace(/^[\u0000\s]+|[\u0000\s]+$/g, ''); + if (line.length === 0) { + // ignore empty lines + return; + } + + // URIs + if (line[0] !== '#') { + this.trigger('data', { + type: 'uri', + uri: line + }); + return; + } + + // Comments + if (line.indexOf('#EXT') !== 0) { + this.trigger('data', { + type: 'comment', + text: line.slice(1) + }); + return; + } + + // strip off any carriage returns here so the regex matching + // doesn't have to account for them. + line = line.replace('\r', ''); + + // Tags + match = /^#EXTM3U/.exec(line); + if (match) { + this.trigger('data', { + type: 'tag', + tagType: 'm3u' + }); + return; + } + match = /^#EXTINF:?([0-9\.]*)?,?(.*)?$/.exec(line); + if (match) { + event = { + type: 'tag', + tagType: 'inf' + }; + if (match[1]) { + event.duration = parseFloat(match[1]); + } + if (match[2]) { + event.title = match[2]; + } + this.trigger('data', event); + return; + } + match = /^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(line); + if (match) { + event = { + type: 'tag', + tagType: 'targetduration' + }; + if (match[1]) { + event.duration = parseInt(match[1], 10); + } + this.trigger('data', event); + return; + } + match = /^#ZEN-TOTAL-DURATION:?([0-9.]*)?/.exec(line); + if (match) { + event = { + type: 'tag', + tagType: 'totalduration' + }; + if (match[1]) { + event.duration = parseInt(match[1], 10); + } + this.trigger('data', event); + return; + } + match = /^#EXT-X-VERSION:?([0-9.]*)?/.exec(line); + if (match) { + event = { + type: 'tag', + tagType: 'version' + }; + if (match[1]) { + event.version = parseInt(match[1], 10); + } + this.trigger('data', event); + return; + } + match = /^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(line); + if (match) { + event = { + type: 'tag', + tagType: 'media-sequence' + }; + if (match[1]) { + event.number = parseInt(match[1], 10); + } + this.trigger('data', event); + return; + } + match = /^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(line); + if (match) { + event = { + type: 'tag', + tagType: 'discontinuity-sequence' + }; + if (match[1]) { + event.number = parseInt(match[1], 10); + } + this.trigger('data', event); + return; + } + match = /^#EXT-X-PLAYLIST-TYPE:?(.*)?$/.exec(line); + if (match) { + event = { + type: 'tag', + tagType: 'playlist-type' + }; + if (match[1]) { + event.playlistType = match[1]; + } + this.trigger('data', event); + return; + } + match = /^#EXT-X-BYTERANGE:?([0-9.]*)?@?([0-9.]*)?/.exec(line); + if (match) { + event = { + type: 'tag', + tagType: 'byterange' + }; + if (match[1]) { + event.length = parseInt(match[1], 10); + } + if (match[2]) { + event.offset = parseInt(match[2], 10); + } + this.trigger('data', event); + return; + } + match = /^#EXT-X-ALLOW-CACHE:?(YES|NO)?/.exec(line); + if (match) { + event = { + type: 'tag', + tagType: 'allow-cache' + }; + if (match[1]) { + event.allowed = !/NO/.test(match[1]); + } + this.trigger('data', event); + return; + } + match = /^#EXT-X-STREAM-INF:?(.*)$/.exec(line); + if (match) { + event = { + type: 'tag', + tagType: 'stream-inf' + }; + if (match[1]) { + event.attributes = parseAttributes(match[1]); + + if (event.attributes.RESOLUTION) { + var split = event.attributes.RESOLUTION.split('x'); + var resolution = {}; + + if (split[0]) { + resolution.width = parseInt(split[0], 10); + } + if (split[1]) { + resolution.height = parseInt(split[1], 10); + } + event.attributes.RESOLUTION = resolution; + } + if (event.attributes.BANDWIDTH) { + event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10); + } + if (event.attributes['PROGRAM-ID']) { + event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10); + } + } + this.trigger('data', event); + return; + } + match = /^#EXT-X-MEDIA:?(.*)$/.exec(line); + if (match) { + event = { + type: 'tag', + tagType: 'media' + }; + if (match[1]) { + event.attributes = parseAttributes(match[1]); + } + this.trigger('data', event); + return; + } + match = /^#EXT-X-ENDLIST/.exec(line); + if (match) { + this.trigger('data', { + type: 'tag', + tagType: 'endlist' + }); + return; + } + match = /^#EXT-X-DISCONTINUITY/.exec(line); + if (match) { + this.trigger('data', { + type: 'tag', + tagType: 'discontinuity' + }); + return; + } + match = /^#EXT-X-KEY:?(.*)$/.exec(line); + if (match) { + event = { + type: 'tag', + tagType: 'key' + }; + if (match[1]) { + event.attributes = parseAttributes(match[1]); + // parse the IV string into a Uint32Array + if (event.attributes.IV) { + if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') { + event.attributes.IV = event.attributes.IV.substring(2); + } + + event.attributes.IV = event.attributes.IV.match(/.{8}/g); + event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16); + event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16); + event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16); + event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16); + event.attributes.IV = new Uint32Array(event.attributes.IV); + } + } + this.trigger('data', event); + return; + } + + // unknown tag type + this.trigger('data', { + type: 'tag', + data: line.slice(4, line.length) + }); + } + }]); + + return ParseStream; +})(_stream2['default']); + +exports['default'] = ParseStream; +module.exports = exports['default']; +},{"./stream":60}],59:[function(require,module,exports){ +/** + * @file m3u8/parser.js + */ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _stream = require('./stream'); + +var _stream2 = _interopRequireDefault(_stream); + +var _lineStream = require('./line-stream'); + +var _lineStream2 = _interopRequireDefault(_lineStream); + +var _parseStream = require('./parse-stream'); + +var _parseStream2 = _interopRequireDefault(_parseStream); + +var _lodashCompatObjectMerge = require('lodash-compat/object/merge'); + +var _lodashCompatObjectMerge2 = _interopRequireDefault(_lodashCompatObjectMerge); + +/** + * A parser for M3U8 files. The current interpretation of the input is + * exposed as a property `manifest` on parser objects. It's just two lines to + * create and parse a manifest once you have the contents available as a string: + * + * ```js + * var parser = new m3u8.Parser(); + * parser.push(xhr.responseText); + * ``` + * + * New input can later be applied to update the manifest object by calling + * `push` again. + * + * The parser attempts to create a usable manifest object even if the + * underlying input is somewhat nonsensical. It emits `info` and `warning` + * events during the parse if it encounters input that seems invalid or + * requires some property of the manifest object to be defaulted. + * + * @class Parser + * @extends Stream + */ + +var Parser = (function (_Stream) { + _inherits(Parser, _Stream); + + function Parser() { + _classCallCheck(this, Parser); + + _get(Object.getPrototypeOf(Parser.prototype), 'constructor', this).call(this); + this.lineStream = new _lineStream2['default'](); + this.parseStream = new _parseStream2['default'](); + this.lineStream.pipe(this.parseStream); + /* eslint-disable consistent-this */ + var self = this; + /* eslint-enable consistent-this */ + var uris = []; + var currentUri = {}; + var _key = undefined; + var noop = function noop() {}; + var defaultMediaGroups = { + 'AUDIO': {}, + 'VIDEO': {}, + 'CLOSED-CAPTIONS': {}, + 'SUBTITLES': {} + }; + // group segments into numbered timelines delineated by discontinuities + var currentTimeline = 0; + + // the manifest is empty until the parse stream begins delivering data + this.manifest = { + allowCache: true, + discontinuityStarts: [] + }; + + // update the manifest with the m3u8 entry from the parse stream + this.parseStream.on('data', function (entry) { + var mediaGroup = undefined; + var rendition = undefined; + + ({ + tag: function tag() { + // switch based on the tag type + (({ + 'allow-cache': function allowCache() { + this.manifest.allowCache = entry.allowed; + if (!('allowed' in entry)) { + this.trigger('info', { + message: 'defaulting allowCache to YES' + }); + this.manifest.allowCache = true; + } + }, + byterange: function byterange() { + var byterange = {}; + + if ('length' in entry) { + currentUri.byterange = byterange; + byterange.length = entry.length; + + if (!('offset' in entry)) { + this.trigger('info', { + message: 'defaulting offset to zero' + }); + entry.offset = 0; + } + } + if ('offset' in entry) { + currentUri.byterange = byterange; + byterange.offset = entry.offset; + } + }, + endlist: function endlist() { + this.manifest.endList = true; + }, + inf: function inf() { + if (!('mediaSequence' in this.manifest)) { + this.manifest.mediaSequence = 0; + this.trigger('info', { + message: 'defaulting media sequence to zero' + }); + } + if (!('discontinuitySequence' in this.manifest)) { + this.manifest.discontinuitySequence = 0; + this.trigger('info', { + message: 'defaulting discontinuity sequence to zero' + }); + } + if (entry.duration > 0) { + currentUri.duration = entry.duration; + } + + if (entry.duration === 0) { + currentUri.duration = 0.01; + this.trigger('info', { + message: 'updating zero segment duration to a small value' + }); + } + + this.manifest.segments = uris; + }, + key: function key() { + if (!entry.attributes) { + this.trigger('warn', { + message: 'ignoring key declaration without attribute list' + }); + return; + } + // clear the active encryption key + if (entry.attributes.METHOD === 'NONE') { + _key = null; + return; + } + if (!entry.attributes.URI) { + this.trigger('warn', { + message: 'ignoring key declaration without URI' + }); + return; + } + if (!entry.attributes.METHOD) { + this.trigger('warn', { + message: 'defaulting key method to AES-128' + }); + } + + // setup an encryption key for upcoming segments + _key = { + method: entry.attributes.METHOD || 'AES-128', + uri: entry.attributes.URI + }; + + if (typeof entry.attributes.IV !== 'undefined') { + _key.iv = entry.attributes.IV; + } + }, + 'media-sequence': function mediaSequence() { + if (!isFinite(entry.number)) { + this.trigger('warn', { + message: 'ignoring invalid media sequence: ' + entry.number + }); + return; + } + this.manifest.mediaSequence = entry.number; + }, + 'discontinuity-sequence': function discontinuitySequence() { + if (!isFinite(entry.number)) { + this.trigger('warn', { + message: 'ignoring invalid discontinuity sequence: ' + entry.number + }); + return; + } + this.manifest.discontinuitySequence = entry.number; + currentTimeline = entry.number; + }, + 'playlist-type': function playlistType() { + if (!/VOD|EVENT/.test(entry.playlistType)) { + this.trigger('warn', { + message: 'ignoring unknown playlist type: ' + entry.playlist + }); + return; + } + this.manifest.playlistType = entry.playlistType; + }, + 'stream-inf': function streamInf() { + this.manifest.playlists = uris; + this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups; + + if (!entry.attributes) { + this.trigger('warn', { + message: 'ignoring empty stream-inf attributes' + }); + return; + } + + if (!currentUri.attributes) { + currentUri.attributes = {}; + } + currentUri.attributes = (0, _lodashCompatObjectMerge2['default'])(currentUri.attributes, entry.attributes); + }, + media: function media() { + this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups; + + if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) { + this.trigger('warn', { + message: 'ignoring incomplete or missing media group' + }); + return; + } + + // find the media group, creating defaults as necessary + var mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE]; + + mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {}; + mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; + + // collect the rendition metadata + rendition = { + 'default': /yes/i.test(entry.attributes.DEFAULT) + }; + if (rendition['default']) { + rendition.autoselect = true; + } else { + rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT); + } + if (entry.attributes.LANGUAGE) { + rendition.language = entry.attributes.LANGUAGE; + } + if (entry.attributes.URI) { + rendition.uri = entry.attributes.URI; + } + + // insert the new rendition + mediaGroup[entry.attributes.NAME] = rendition; + }, + discontinuity: function discontinuity() { + currentTimeline += 1; + currentUri.discontinuity = true; + this.manifest.discontinuityStarts.push(uris.length); + }, + targetduration: function targetduration() { + if (!isFinite(entry.duration) || entry.duration < 0) { + this.trigger('warn', { + message: 'ignoring invalid target duration: ' + entry.duration + }); + return; + } + this.manifest.targetDuration = entry.duration; + }, + totalduration: function totalduration() { + if (!isFinite(entry.duration) || entry.duration < 0) { + this.trigger('warn', { + message: 'ignoring invalid total duration: ' + entry.duration + }); + return; + } + this.manifest.totalDuration = entry.duration; + } + })[entry.tagType] || noop).call(self); + }, + uri: function uri() { + currentUri.uri = entry.uri; + uris.push(currentUri); + + // if no explicit duration was declared, use the target duration + if (this.manifest.targetDuration && !('duration' in currentUri)) { + this.trigger('warn', { + message: 'defaulting segment duration to the target duration' + }); + currentUri.duration = this.manifest.targetDuration; + } + // annotate with encryption information, if necessary + if (_key) { + currentUri.key = _key; + } + currentUri.timeline = currentTimeline; + + // prepare for the next URI + currentUri = {}; + }, + comment: function comment() { + // comments are not important for playback + } + })[entry.type].call(self); + }); + } + + /** + * Parse the input string and update the manifest object. + * + * @param {String} chunk a potentially incomplete portion of the manifest + */ + + _createClass(Parser, [{ + key: 'push', + value: function push(chunk) { + this.lineStream.push(chunk); + } + + /** + * Flush any remaining input. This can be handy if the last line of an M3U8 + * manifest did not contain a trailing newline but the file has been + * completely received. + */ + }, { + key: 'end', + value: function end() { + // flush any buffered input + this.lineStream.push('\n'); + } + }]); + + return Parser; +})(_stream2['default']); + +exports['default'] = Parser; +module.exports = exports['default']; +},{"./line-stream":57,"./parse-stream":58,"./stream":60,"lodash-compat/object/merge":53}],60:[function(require,module,exports){ +arguments[4][16][0].apply(exports,arguments) +},{"dup":16}],61:[function(require,module,exports){ +/* + * pkcs7.pad + * https://github.com/brightcove/pkcs7 + * + * Copyright (c) 2014 Brightcove + * Licensed under the apache2 license. + */ + +'use strict'; + +var PADDING; + +/** + * Returns a new Uint8Array that is padded with PKCS#7 padding. + * @param plaintext {Uint8Array} the input bytes before encryption + * @return {Uint8Array} the padded bytes + * @see http://tools.ietf.org/html/rfc5652 + */ +module.exports = function pad(plaintext) { + var padding = PADDING[(plaintext.byteLength % 16) || 0], + result = new Uint8Array(plaintext.byteLength + padding.length); + result.set(plaintext); + result.set(padding, plaintext.byteLength); + return result; +}; + +// pre-define the padding values +PADDING = [ + [16, 16, 16, 16, + 16, 16, 16, 16, + 16, 16, 16, 16, + 16, 16, 16, 16], + + [15, 15, 15, 15, + 15, 15, 15, 15, + 15, 15, 15, 15, + 15, 15, 15], + + [14, 14, 14, 14, + 14, 14, 14, 14, + 14, 14, 14, 14, + 14, 14], + + [13, 13, 13, 13, + 13, 13, 13, 13, + 13, 13, 13, 13, + 13], + + [12, 12, 12, 12, + 12, 12, 12, 12, + 12, 12, 12, 12], + + [11, 11, 11, 11, + 11, 11, 11, 11, + 11, 11, 11], + + [10, 10, 10, 10, + 10, 10, 10, 10, + 10, 10], + + [9, 9, 9, 9, + 9, 9, 9, 9, + 9], + + [8, 8, 8, 8, + 8, 8, 8, 8], + + [7, 7, 7, 7, + 7, 7, 7], + + [6, 6, 6, 6, + 6, 6], + + [5, 5, 5, 5, + 5], + + [4, 4, 4, 4], + + [3, 3, 3], + + [2, 2], + + [1] +]; + +},{}],62:[function(require,module,exports){ +/* + * pkcs7 + * https://github.com/brightcove/pkcs7 + * + * Copyright (c) 2014 Brightcove + * Licensed under the apache2 license. + */ + +'use strict'; + +exports.pad = require('./pad.js'); +exports.unpad = require('./unpad.js'); + +},{"./pad.js":61,"./unpad.js":63}],63:[function(require,module,exports){ +/* + * pkcs7.unpad + * https://github.com/brightcove/pkcs7 + * + * Copyright (c) 2014 Brightcove + * Licensed under the apache2 license. + */ + +'use strict'; + +/** + * Returns the subarray of a Uint8Array without PKCS#7 padding. + * @param padded {Uint8Array} unencrypted bytes that have been padded + * @return {Uint8Array} the unpadded bytes + * @see http://tools.ietf.org/html/rfc5652 + */ +module.exports = function unpad(padded) { + return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]); +}; + +},{}],64:[function(require,module,exports){ +(function (global){ +/** + * @file add-text-track-data.js + */ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); + +var _videoJs2 = _interopRequireDefault(_videoJs); + +/** + * Define properties on a cue for backwards compatability, + * but warn the user that the way that they are using it + * is depricated and will be removed at a later date. + * + * @param {Cue} cue the cue to add the properties on + * @private + */ +var deprecateOldCue = function deprecateOldCue(cue) { + Object.defineProperties(cue.frame, { + id: { + get: function get() { + _videoJs2['default'].log.warn('cue.frame.id is deprecated. Use cue.value.key instead.'); + return cue.value.key; + } + }, + value: { + get: function get() { + _videoJs2['default'].log.warn('cue.frame.value is deprecated. Use cue.value.data instead.'); + return cue.value.data; + } + }, + privateData: { + get: function get() { + _videoJs2['default'].log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.'); + return cue.value.data; + } + } + }); +}; + +/** + * Add text track data to a source handler given the captions and + * metadata from the buffer. + * + * @param {Object} sourceHandler the flash or virtual source buffer + * @param {Array} captionArray an array of caption data + * @param {Array} cue an array of meta data + * @private + */ +var addTextTrackData = function addTextTrackData(sourceHandler, captionArray, metadataArray) { + var Cue = window.WebKitDataCue || window.VTTCue; + + if (captionArray) { + captionArray.forEach(function (caption) { + this.inbandTextTrack_.addCue(new Cue(caption.startTime + this.timestampOffset, caption.endTime + this.timestampOffset, caption.text)); + }, sourceHandler); + } + + if (metadataArray) { + metadataArray.forEach(function (metadata) { + var time = metadata.cueTime + this.timestampOffset; + + metadata.frames.forEach(function (frame) { + var cue = new Cue(time, time, frame.value || frame.url || frame.data || ''); + + cue.frame = frame; + cue.value = frame; + deprecateOldCue(cue); + this.metadataTrack_.addCue(cue); + }, this); + }, sourceHandler); + } +}; + +exports['default'] = addTextTrackData; +module.exports = exports['default']; +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],65:[function(require,module,exports){ +/** + * @file codec-utils.js + */ + +/** + * Check if a codec string refers to an audio codec. + * + * @param {String} codec codec string to check + * @return {Boolean} if this is an audio codec + * @private + */ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); +var isAudioCodec = function isAudioCodec(codec) { + return (/mp4a\.\d+.\d+/i.test(codec) + ); +}; + +/** + * Check if a codec string refers to a video codec. + * + * @param {String} codec codec string to check + * @return {Boolean} if this is a video codec + * @private + */ +var isVideoCodec = function isVideoCodec(codec) { + return (/avc1\.[\da-f]+/i.test(codec) + ); +}; + +/** + * Parse a content type header into a type and parameters + * object + * + * @param {String} type the content type header + * @return {Object} the parsed content-type + * @private + */ +var parseContentType = function parseContentType(type) { + var object = { type: '', parameters: {} }; + var parameters = type.trim().split(';'); + + // first parameter should always be content-type + object.type = parameters.shift().trim(); + parameters.forEach(function (parameter) { + var pair = parameter.trim().split('='); + + if (pair.length > 1) { + var _name = pair[0].replace(/"/g, '').trim(); + var value = pair[1].replace(/"/g, '').trim(); + + object.parameters[_name] = value; + } + }); + + return object; +}; + +exports['default'] = { + isAudioCodec: isAudioCodec, + parseContentType: parseContentType, + isVideoCodec: isVideoCodec +}; +module.exports = exports['default']; +},{}],66:[function(require,module,exports){ +/** + * @file create-text-tracks-if-necessary.js + */ + +/** + * Create text tracks on video.js if they exist on a segment. + * + * @param {Object} sourceBuffer the VSB or FSB + * @param {Object} mediaSource the HTML or Flash media source + * @param {Object} segment the segment that may contain the text track + * @private + */ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); +var createTextTracksIfNecessary = function createTextTracksIfNecessary(sourceBuffer, mediaSource, segment) { + // create an in-band caption track if one is present in the segment + if (segment.captions && segment.captions.length && !sourceBuffer.inbandTextTrack_) { + sourceBuffer.inbandTextTrack_ = mediaSource.player_.addTextTrack('captions', 'cc1'); + } + + if (segment.metadata && segment.metadata.length && !sourceBuffer.metadataTrack_) { + sourceBuffer.metadataTrack_ = mediaSource.player_.addTextTrack('metadata', 'Timed Metadata'); + sourceBuffer.metadataTrack_.inBandMetadataTrackDispatchType = segment.metadata.dispatchType; + } +}; + +exports['default'] = createTextTracksIfNecessary; +module.exports = exports['default']; +},{}],67:[function(require,module,exports){ +/** + * @file flash-constants.js + */ +/** + * The maximum size in bytes for append operations to the video.js + * SWF. Calling through to Flash blocks and can be expensive so + * tuning this parameter may improve playback on slower + * systems. There are two factors to consider: + * - Each interaction with the SWF must be quick or you risk dropping + * video frames. To maintain 60fps for the rest of the page, each append + * must not take longer than 16ms. Given the likelihood that the page + * will be executing more javascript than just playback, you probably + * want to aim for less than 8ms. We aim for just 4ms. + * - Bigger appends significantly increase throughput. The total number of + * bytes over time delivered to the SWF must exceed the video bitrate or + * playback will stall. + * + * We adaptively tune the size of appends to give the best throughput + * possible given the performance of the system. To do that we try to append + * as much as possible in TIME_PER_TICK and while tuning the size of appends + * dynamically so that we only append about 4-times in that 4ms span. + * + * The reason we try to keep the number of appends around four is due to + * externalities such as Flash load and garbage collection that are highly + * variable and having 4 iterations allows us to exit the loop early if + * an iteration takes longer than expected. + * + * @private + */ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +var flashConstants = { + TIME_BETWEEN_TICKS: Math.floor(1000 / 480), + TIME_PER_TICK: Math.floor(1000 / 240), + // 1kb + BYTES_PER_CHUNK: 1 * 1024, + MIN_CHUNK: 1024, + MAX_CHUNK: 1024 * 1024 +}; + +exports["default"] = flashConstants; +module.exports = exports["default"]; +},{}],68:[function(require,module,exports){ +(function (global){ +/** + * @file flash-media-source.js + */ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); + +var _videoJs2 = _interopRequireDefault(_videoJs); + +var _flashSourceBuffer = require('./flash-source-buffer'); + +var _flashSourceBuffer2 = _interopRequireDefault(_flashSourceBuffer); + +var _flashConstants = require('./flash-constants'); + +var _flashConstants2 = _interopRequireDefault(_flashConstants); + +var _codecUtils = require('./codec-utils'); + +/** + * A flash implmentation of HTML MediaSources and a polyfill + * for browsers that don't support native or HTML MediaSources.. + * + * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource + * @class FlashMediaSource + * @extends videojs.EventTarget + */ + +var FlashMediaSource = (function (_videojs$EventTarget) { + _inherits(FlashMediaSource, _videojs$EventTarget); + + function FlashMediaSource() { + var _this = this; + + _classCallCheck(this, FlashMediaSource); + + _get(Object.getPrototypeOf(FlashMediaSource.prototype), 'constructor', this).call(this); + this.sourceBuffers = []; + this.readyState = 'closed'; + + this.on(['sourceopen', 'webkitsourceopen'], function (event) { + // find the swf where we will push media data + _this.swfObj = document.getElementById(event.swfId); + _this.player_ = (0, _videoJs2['default'])(_this.swfObj.parentNode); + _this.tech_ = _this.swfObj.tech; + _this.readyState = 'open'; + + _this.tech_.on('seeking', function () { + var i = _this.sourceBuffers.length; + + while (i--) { + _this.sourceBuffers[i].abort(); + } + }); + + // trigger load events + if (_this.swfObj) { + _this.swfObj.vjs_load(); + } + }); + } + + /** + * Set or return the presentation duration. + * + * @param {Double} value the duration of the media in seconds + * @param {Double} the current presentation duration + * @link http://www.w3.org/TR/media-source/#widl-MediaSource-duration + */ + + /** + * We have this function so that the html and flash interfaces + * are the same. + * + * @private + */ + + _createClass(FlashMediaSource, [{ + key: 'addSeekableRange_', + value: function addSeekableRange_() {} + // intentional no-op + + /** + * Create a new flash source buffer and add it to our flash media source. + * + * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer + * @param {String} type the content-type of the source + * @return {Object} the flash source buffer + */ + + }, { + key: 'addSourceBuffer', + value: function addSourceBuffer(type) { + var parsedType = (0, _codecUtils.parseContentType)(type); + var sourceBuffer = undefined; + + // if this is an FLV type, we'll push data to flash + if (parsedType.type === 'video/mp2t') { + // Flash source buffers + sourceBuffer = new _flashSourceBuffer2['default'](this); + } else { + throw new Error('NotSupportedError (Video.js)'); + } + + this.sourceBuffers.push(sourceBuffer); + return sourceBuffer; + } + + /** + * Signals the end of the stream. + * + * @link https://w3c.github.io/media-source/#widl-MediaSource-endOfStream-void-EndOfStreamError-error + * @param {String=} error Signals that a playback error + * has occurred. If specified, it must be either "network" or + * "decode". + */ + }, { + key: 'endOfStream', + value: function endOfStream(error) { + if (error === 'network') { + // MEDIA_ERR_NETWORK + this.tech_.error(2); + } else if (error === 'decode') { + // MEDIA_ERR_DECODE + this.tech_.error(3); + } + if (this.readyState !== 'ended') { + this.readyState = 'ended'; + this.swfObj.vjs_endOfStream(); + } + } + }]); + + return FlashMediaSource; +})(_videoJs2['default'].EventTarget); + +exports['default'] = FlashMediaSource; +try { + Object.defineProperty(FlashMediaSource.prototype, 'duration', { + /** + * Return the presentation duration. + * + * @return {Double} the duration of the media in seconds + * @link http://www.w3.org/TR/media-source/#widl-MediaSource-duration + */ + get: function get() { + if (!this.swfObj) { + return NaN; + } + // get the current duration from the SWF + return this.swfObj.vjs_getProperty('duration'); + }, + /** + * Set the presentation duration. + * + * @param {Double} value the duration of the media in seconds + * @return {Double} the duration of the media in seconds + * @link http://www.w3.org/TR/media-source/#widl-MediaSource-duration + */ + set: function set(value) { + var i = undefined; + var oldDuration = this.swfObj.vjs_getProperty('duration'); + + this.swfObj.vjs_setProperty('duration', value); + + if (value < oldDuration) { + // In MSE, this triggers the range removal algorithm which causes + // an update to occur + for (i = 0; i < this.sourceBuffers.length; i++) { + this.sourceBuffers[i].remove(value, oldDuration); + } + } + + return value; + } + }); +} catch (e) { + // IE8 throws if defineProperty is called on a non-DOM node. We + // don't support IE8 but we shouldn't throw an error if loaded + // there. + FlashMediaSource.prototype.duration = NaN; +} + +for (var property in _flashConstants2['default']) { + FlashMediaSource[property] = _flashConstants2['default'][property]; +} +module.exports = exports['default']; +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./codec-utils":65,"./flash-constants":67,"./flash-source-buffer":69}],69:[function(require,module,exports){ +(function (global){ +/** + * @file flash-source-buffer.js + */ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); + +var _videoJs2 = _interopRequireDefault(_videoJs); + +var _muxJsLibFlv = require('mux.js/lib/flv'); + +var _muxJsLibFlv2 = _interopRequireDefault(_muxJsLibFlv); + +var _removeCuesFromTrack = require('./remove-cues-from-track'); + +var _removeCuesFromTrack2 = _interopRequireDefault(_removeCuesFromTrack); + +var _createTextTracksIfNecessary = require('./create-text-tracks-if-necessary'); + +var _createTextTracksIfNecessary2 = _interopRequireDefault(_createTextTracksIfNecessary); + +var _addTextTrackData = require('./add-text-track-data'); + +var _addTextTrackData2 = _interopRequireDefault(_addTextTrackData); + +var _flashConstants = require('./flash-constants'); + +var _flashConstants2 = _interopRequireDefault(_flashConstants); + +/** + * A wrapper around the setTimeout function that uses + * the flash constant time between ticks value. + * + * @param {Function} func the function callback to run + * @private + */ +var scheduleTick = function scheduleTick(func) { + // Chrome doesn't invoke requestAnimationFrame callbacks + // in background tabs, so use setTimeout. + window.setTimeout(func, _flashConstants2['default'].TIME_BETWEEN_TICKS); +}; + +/** + * Round a number to a specified number of places much like + * toFixed but return a number instead of a string representation. + * + * @param {Number} num A number + * @param {Number} places The number of decimal places which to + * round + * @private + */ +var toDecimalPlaces = function toDecimalPlaces(num, places) { + if (typeof places !== 'number' || places < 0) { + places = 0; + } + + var scale = Math.pow(10, places); + + return Math.round(num * scale) / scale; +}; + +/** + * A SourceBuffer implementation for Flash rather than HTML. + * + * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource + * @param {Object} mediaSource the flash media source + * @class FlashSourceBuffer + * @extends videojs.EventTarget + */ + +var FlashSourceBuffer = (function (_videojs$EventTarget) { + _inherits(FlashSourceBuffer, _videojs$EventTarget); + + function FlashSourceBuffer(mediaSource) { + var _this = this; + + _classCallCheck(this, FlashSourceBuffer); + + _get(Object.getPrototypeOf(FlashSourceBuffer.prototype), 'constructor', this).call(this); + var encodedHeader = undefined; + + // Start off using the globally defined value but refine + // as we append data into flash + this.chunkSize_ = _flashConstants2['default'].BYTES_PER_CHUNK; + + // byte arrays queued to be appended + this.buffer_ = []; + + // the total number of queued bytes + this.bufferSize_ = 0; + + // to be able to determine the correct position to seek to, we + // need to retain information about the mapping between the + // media timeline and PTS values + this.basePtsOffset_ = NaN; + + this.mediaSource = mediaSource; + + // indicates whether the asynchronous continuation of an operation + // is still being processed + // see https://w3c.github.io/media-source/#widl-SourceBuffer-updating + this.updating = false; + this.timestampOffset_ = 0; + + // TS to FLV transmuxer + this.segmentParser_ = new _muxJsLibFlv2['default'].Transmuxer(); + this.segmentParser_.on('data', this.receiveBuffer_.bind(this)); + encodedHeader = window.btoa(String.fromCharCode.apply(null, Array.prototype.slice.call(this.segmentParser_.getFlvHeader()))); + this.mediaSource.swfObj.vjs_appendBuffer(encodedHeader); + + Object.defineProperty(this, 'timestampOffset', { + get: function get() { + return this.timestampOffset_; + }, + set: function set(val) { + if (typeof val === 'number' && val >= 0) { + this.timestampOffset_ = val; + this.segmentParser_ = new _muxJsLibFlv2['default'].Transmuxer(); + this.segmentParser_.on('data', this.receiveBuffer_.bind(this)); + // We have to tell flash to expect a discontinuity + this.mediaSource.swfObj.vjs_discontinuity(); + // the media <-> PTS mapping must be re-established after + // the discontinuity + this.basePtsOffset_ = NaN; + } + } + }); + + Object.defineProperty(this, 'buffered', { + get: function get() { + if (!this.mediaSource || !this.mediaSource.swfObj || !('vjs_getProperty' in this.mediaSource.swfObj)) { + return _videoJs2['default'].createTimeRange(); + } + + var buffered = this.mediaSource.swfObj.vjs_getProperty('buffered'); + + if (buffered && buffered.length) { + buffered[0][0] = toDecimalPlaces(buffered[0][0], 3); + buffered[0][1] = toDecimalPlaces(buffered[0][1], 3); + } + return _videoJs2['default'].createTimeRanges(buffered); + } + }); + + // On a seek we remove all text track data since flash has no concept + // of a buffered-range and everything else is reset on seek + this.mediaSource.player_.on('seeked', function () { + (0, _removeCuesFromTrack2['default'])(0, Infinity, _this.metadataTrack_); + (0, _removeCuesFromTrack2['default'])(0, Infinity, _this.inbandTextTrack_); + }); + } + + /** + * Append bytes to the sourcebuffers buffer, in this case we + * have to append it to swf object. + * + * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer + * @param {Array} bytes + */ + + _createClass(FlashSourceBuffer, [{ + key: 'appendBuffer', + value: function appendBuffer(bytes) { + var _this2 = this; + + var error = undefined; + var chunk = 512 * 1024; + var i = 0; + + if (this.updating) { + error = new Error('SourceBuffer.append() cannot be called ' + 'while an update is in progress'); + error.name = 'InvalidStateError'; + error.code = 11; + throw error; + } + + this.updating = true; + this.mediaSource.readyState = 'open'; + this.trigger({ type: 'update' }); + + // this is here to use recursion + var chunkInData = function chunkInData() { + _this2.segmentParser_.push(bytes.subarray(i, i + chunk)); + i += chunk; + if (i < bytes.byteLength) { + scheduleTick(chunkInData); + } else { + scheduleTick(_this2.segmentParser_.flush.bind(_this2.segmentParser_)); + } + }; + + chunkInData(); + } + + /** + * Reset the parser and remove any data queued to be sent to the SWF. + * + * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/abort + */ + }, { + key: 'abort', + value: function abort() { + this.buffer_ = []; + this.bufferSize_ = 0; + this.mediaSource.swfObj.vjs_abort(); + + // report any outstanding updates have ended + if (this.updating) { + this.updating = false; + this.trigger({ type: 'updateend' }); + } + } + + /** + * Flash cannot remove ranges already buffered in the NetStream + * but seeking clears the buffer entirely. For most purposes, + * having this operation act as a no-op is acceptable. + * + * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/remove + * @param {Double} start start of the section to remove + * @param {Double} end end of the section to remove + */ + }, { + key: 'remove', + value: function remove(start, end) { + (0, _removeCuesFromTrack2['default'])(start, end, this.metadataTrack_); + (0, _removeCuesFromTrack2['default'])(start, end, this.inbandTextTrack_); + this.trigger({ type: 'update' }); + this.trigger({ type: 'updateend' }); + } + + /** + * Receive a buffer from the flv. + * + * @param {Object} segment + * @private + */ + }, { + key: 'receiveBuffer_', + value: function receiveBuffer_(segment) { + var _this3 = this; + + // create an in-band caption track if one is present in the segment + (0, _createTextTracksIfNecessary2['default'])(this, this.mediaSource, segment); + (0, _addTextTrackData2['default'])(this, segment.captions, segment.metadata); + + // Do this asynchronously since convertTagsToData_ can be time consuming + scheduleTick(function () { + var flvBytes = _this3.convertTagsToData_(segment); + + if (_this3.buffer_.length === 0) { + scheduleTick(_this3.processBuffer_.bind(_this3)); + } + + if (flvBytes) { + _this3.buffer_.push(flvBytes); + _this3.bufferSize_ += flvBytes.byteLength; + } + }); + } + + /** + * Append a portion of the current buffer to the SWF. + * + * @private + */ + }, { + key: 'processBuffer_', + value: function processBuffer_() { + var chunk = undefined; + var i = undefined; + var length = undefined; + var binary = undefined; + var b64str = undefined; + var startByte = 0; + var appendIterations = 0; + var startTime = +new Date(); + var appendTime = undefined; + + if (!this.buffer_.length) { + if (this.updating !== false) { + this.updating = false; + this.trigger({ type: 'updateend' }); + } + // do nothing if the buffer is empty + return; + } + + do { + appendIterations++; + // concatenate appends up to the max append size + chunk = this.buffer_[0].subarray(startByte, startByte + this.chunkSize_); + + // requeue any bytes that won't make it this round + if (chunk.byteLength < this.chunkSize_ || this.buffer_[0].byteLength === startByte + this.chunkSize_) { + startByte = 0; + this.buffer_.shift(); + } else { + startByte += this.chunkSize_; + } + + this.bufferSize_ -= chunk.byteLength; + + // base64 encode the bytes + binary = ''; + length = chunk.byteLength; + for (i = 0; i < length; i++) { + binary += String.fromCharCode(chunk[i]); + } + b64str = window.btoa(binary); + + // bypass normal ExternalInterface calls and pass xml directly + // IE can be slow by default + this.mediaSource.swfObj.CallFunction('' + b64str + ''); + appendTime = new Date() - startTime; + } while (this.buffer_.length && appendTime < _flashConstants2['default'].TIME_PER_TICK); + + if (this.buffer_.length && startByte) { + this.buffer_[0] = this.buffer_[0].subarray(startByte); + } + + if (appendTime >= _flashConstants2['default'].TIME_PER_TICK) { + // We want to target 4 iterations per time-slot so that gives us + // room to adjust to changes in Flash load and other externalities + // such as garbage collection while still maximizing throughput + this.chunkSize_ = Math.floor(this.chunkSize_ * (appendIterations / 4)); + } + + // We also make sure that the chunk-size doesn't drop below 1KB or + // go above 1MB as a sanity check + this.chunkSize_ = Math.max(_flashConstants2['default'].MIN_CHUNK, Math.min(this.chunkSize_, _flashConstants2['default'].MAX_CHUNK)); + + // schedule another append if necessary + if (this.bufferSize_ !== 0) { + scheduleTick(this.processBuffer_.bind(this)); + } else { + this.updating = false; + this.trigger({ type: 'updateend' }); + } + } + + /** + * Turns an array of flv tags into a Uint8Array representing the + * flv data. Also removes any tags that are before the current + * time so that playback begins at or slightly after the right + * place on a seek + * + * @private + * @param {Object} segmentData object of segment data + */ + }, { + key: 'convertTagsToData_', + value: function convertTagsToData_(segmentData) { + var segmentByteLength = 0; + var tech = this.mediaSource.tech_; + var targetPts = 0; + var i = undefined; + var j = undefined; + var segment = undefined; + var filteredTags = []; + var tags = this.getOrderedTags_(segmentData); + + // Establish the media timeline to PTS translation if we don't + // have one already + if (isNaN(this.basePtsOffset_) && tags.length) { + this.basePtsOffset_ = tags[0].pts; + } + + // Trim any tags that are before the end of the end of + // the current buffer + if (tech.buffered().length) { + targetPts = tech.buffered().end(0) - this.timestampOffset; + } + // Trim to currentTime if it's ahead of buffered or buffered doesn't exist + targetPts = Math.max(targetPts, tech.currentTime() - this.timestampOffset); + + // PTS values are represented in milliseconds + targetPts *= 1e3; + targetPts += this.basePtsOffset_; + + // skip tags with a presentation time less than the seek target + for (i = 0; i < tags.length; i++) { + if (tags[i].pts >= targetPts) { + filteredTags.push(tags[i]); + } + } + + if (filteredTags.length === 0) { + return; + } + + // concatenate the bytes into a single segment + for (i = 0; i < filteredTags.length; i++) { + segmentByteLength += filteredTags[i].bytes.byteLength; + } + segment = new Uint8Array(segmentByteLength); + for (i = 0, j = 0; i < filteredTags.length; i++) { + segment.set(filteredTags[i].bytes, j); + j += filteredTags[i].bytes.byteLength; + } + + return segment; + } + + /** + * Assemble the FLV tags in decoder order. + * + * @private + * @param {Object} segmentData object of segment data + */ + }, { + key: 'getOrderedTags_', + value: function getOrderedTags_(segmentData) { + var videoTags = segmentData.tags.videoTags; + var audioTags = segmentData.tags.audioTags; + var tag = undefined; + var tags = []; + + while (videoTags.length || audioTags.length) { + if (!videoTags.length) { + // only audio tags remain + tag = audioTags.shift(); + } else if (!audioTags.length) { + // only video tags remain + tag = videoTags.shift(); + } else if (audioTags[0].dts < videoTags[0].dts) { + // audio should be decoded next + tag = audioTags.shift(); + } else { + // video should be decoded next + tag = videoTags.shift(); + } + + tags.push(tag.finalize()); + } + + return tags; + } + }]); + + return FlashSourceBuffer; +})(_videoJs2['default'].EventTarget); + +exports['default'] = FlashSourceBuffer; +module.exports = exports['default']; +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./add-text-track-data":64,"./create-text-tracks-if-necessary":66,"./flash-constants":67,"./remove-cues-from-track":71,"mux.js/lib/flv":79}],70:[function(require,module,exports){ +(function (global){ +/** + * @file html-media-source.js + */ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); + +var _videoJs2 = _interopRequireDefault(_videoJs); + +var _virtualSourceBuffer = require('./virtual-source-buffer'); + +var _virtualSourceBuffer2 = _interopRequireDefault(_virtualSourceBuffer); + +var _codecUtils = require('./codec-utils'); + +/** + * Replace the old apple-style `avc1.
    .
    ` codec string with the standard + * `avc1.` + * + * @param {Array} codecs an array of codec strings to fix + * @return {Array} the translated codec array + * @private + */ +var translateLegacyCodecs = function translateLegacyCodecs(codecs) { + return codecs.map(function (codec) { + return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) { + var profileHex = ('00' + Number(profile).toString(16)).slice(-2); + var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2); + + return 'avc1.' + profileHex + '00' + avcLevelHex; + }); + }); +}; + +/** + * Our MediaSource implementation in HTML, mimics native + * MediaSource where/if possible. + * + * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource + * @class HtmlMediaSource + * @extends videojs.EventTarget + */ + +var HtmlMediaSource = (function (_videojs$EventTarget) { + _inherits(HtmlMediaSource, _videojs$EventTarget); + + function HtmlMediaSource() { + var _this = this; + + _classCallCheck(this, HtmlMediaSource); + + _get(Object.getPrototypeOf(HtmlMediaSource.prototype), 'constructor', this).call(this); + var property = undefined; + + this.nativeMediaSource_ = new window.MediaSource(); + // delegate to the native MediaSource's methods by default + for (property in this.nativeMediaSource_) { + if (!(property in HtmlMediaSource.prototype) && typeof this.nativeMediaSource_[property] === 'function') { + this[property] = this.nativeMediaSource_[property].bind(this.nativeMediaSource_); + } + } + + // emulate `duration` and `seekable` until seeking can be + // handled uniformly for live streams + // see https://github.com/w3c/media-source/issues/5 + this.duration_ = NaN; + Object.defineProperty(this, 'duration', { + get: function get() { + if (this.duration_ === Infinity) { + return this.duration_; + } + return this.nativeMediaSource_.duration; + }, + set: function set(duration) { + this.duration_ = duration; + if (duration !== Infinity) { + this.nativeMediaSource_.duration = duration; + return; + } + } + }); + Object.defineProperty(this, 'seekable', { + get: function get() { + if (this.duration_ === Infinity) { + return _videoJs2['default'].createTimeRanges([[0, this.nativeMediaSource_.duration]]); + } + return this.nativeMediaSource_.seekable; + } + }); + + Object.defineProperty(this, 'readyState', { + get: function get() { + return this.nativeMediaSource_.readyState; + } + }); + + Object.defineProperty(this, 'activeSourceBuffers', { + get: function get() { + return this.activeSourceBuffers_; + } + }); + + // the list of virtual and native SourceBuffers created by this + // MediaSource + this.sourceBuffers = []; + + this.activeSourceBuffers_ = []; + + /** + * update the list of active source buffers based upon various + * imformation from HLS and video.js + * + * @private + */ + this.updateActiveSourceBuffers_ = function () { + // Retain the reference but empty the array + _this.activeSourceBuffers_.length = 0; + + // By default, the audio in the combined virtual source buffer is enabled + // and the audio-only source buffer (if it exists) is disabled. + var combined = false; + var audioOnly = true; + + // TODO: maybe we can store the sourcebuffers on the track objects? + // safari may do something like this + for (var i = 0; i < _this.player_.audioTracks().length; i++) { + var track = _this.player_.audioTracks()[i]; + + if (track.enabled && track.kind !== 'main') { + // The enabled track is an alternate audio track so disable the audio in + // the combined source buffer and enable the audio-only source buffer. + combined = true; + audioOnly = false; + break; + } + } + + // Since we currently support a max of two source buffers, add all of the source + // buffers (in order). + _this.sourceBuffers.forEach(function (sourceBuffer) { + /* eslinst-disable */ + // TODO once codecs are required, we can switch to using the codecs to determine + // what stream is the video stream, rather than relying on videoTracks + /* eslinst-enable */ + + if (sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) { + // combined + sourceBuffer.audioDisabled_ = combined; + } else if (sourceBuffer.videoCodec_ && !sourceBuffer.audioCodec_) { + // If the "combined" source buffer is video only, then we do not want + // disable the audio-only source buffer (this is mostly for demuxed + // audio and video hls) + sourceBuffer.audioDisabled_ = true; + audioOnly = false; + } else if (!sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) { + // audio only + sourceBuffer.audioDisabled_ = audioOnly; + if (audioOnly) { + return; + } + } + + _this.activeSourceBuffers_.push(sourceBuffer); + }); + }; + + // Re-emit MediaSource events on the polyfill + ['sourceopen', 'sourceclose', 'sourceended'].forEach(function (eventName) { + this.nativeMediaSource_.addEventListener(eventName, this.trigger.bind(this)); + }, this); + + // capture the associated player when the MediaSource is + // successfully attached + this.on('sourceopen', function (event) { + // Get the player this MediaSource is attached to + var video = document.querySelector('[src="' + _this.url_ + '"]'); + + if (!video) { + return; + } + + _this.player_ = (0, _videoJs2['default'])(video.parentNode); + + if (_this.player_.audioTracks && _this.player_.audioTracks()) { + _this.player_.audioTracks().on('change', _this.updateActiveSourceBuffers_); + _this.player_.audioTracks().on('addtrack', _this.updateActiveSourceBuffers_); + _this.player_.audioTracks().on('removetrack', _this.updateActiveSourceBuffers_); + } + }); + + // explicitly terminate any WebWorkers that were created + // by SourceHandlers + this.on('sourceclose', function (event) { + this.sourceBuffers.forEach(function (sourceBuffer) { + if (sourceBuffer.transmuxer_) { + sourceBuffer.transmuxer_.terminate(); + } + }); + this.sourceBuffers.length = 0; + if (!this.player_) { + return; + } + + if (this.player_.audioTracks && this.player_.audioTracks()) { + this.player_.audioTracks().off('change', this.updateActiveSourceBuffers_); + this.player_.audioTracks().off('addtrack', this.updateActiveSourceBuffers_); + this.player_.audioTracks().off('removetrack', this.updateActiveSourceBuffers_); + } + }); + } + + /** + * Add a range that that can now be seeked to. + * + * @param {Double} start where to start the addition + * @param {Double} end where to end the addition + * @private + */ + + _createClass(HtmlMediaSource, [{ + key: 'addSeekableRange_', + value: function addSeekableRange_(start, end) { + var error = undefined; + + if (this.duration !== Infinity) { + error = new Error('MediaSource.addSeekableRange() can only be invoked ' + 'when the duration is Infinity'); + error.name = 'InvalidStateError'; + error.code = 11; + throw error; + } + + if (end > this.nativeMediaSource_.duration || isNaN(this.nativeMediaSource_.duration)) { + this.nativeMediaSource_.duration = end; + } + } + + /** + * Add a source buffer to the media source. + * + * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer + * @param {String} type the content-type of the content + * @return {Object} the created source buffer + */ + }, { + key: 'addSourceBuffer', + value: function addSourceBuffer(type) { + var buffer = undefined; + var parsedType = (0, _codecUtils.parseContentType)(type); + + // Create a VirtualSourceBuffer to transmux MPEG-2 transport + // stream segments into fragmented MP4s + if (parsedType.type === 'video/mp2t') { + var codecs = []; + + if (parsedType.parameters && parsedType.parameters.codecs) { + codecs = parsedType.parameters.codecs.split(','); + codecs = translateLegacyCodecs(codecs); + codecs = codecs.filter(function (codec) { + return (0, _codecUtils.isAudioCodec)(codec) || (0, _codecUtils.isVideoCodec)(codec); + }); + } + + if (codecs.length === 0) { + codecs = ['avc1.4d400d', 'mp4a.40.2']; + } + + buffer = new _virtualSourceBuffer2['default'](this, codecs); + + if (this.sourceBuffers.length !== 0) { + // If another VirtualSourceBuffer already exists, then we are creating a + // SourceBuffer for an alternate audio track and therefore we know that + // the source has both an audio and video track. + // That means we should trigger the manual creation of the real + // SourceBuffers instead of waiting for the transmuxer to return data + this.sourceBuffers[0].createRealSourceBuffers_(); + buffer.createRealSourceBuffers_(); + + // Automatically disable the audio on the first source buffer if + // a second source buffer is ever created + this.sourceBuffers[0].audioDisabled_ = true; + } + } else { + // delegate to the native implementation + buffer = this.nativeMediaSource_.addSourceBuffer(type); + } + + this.sourceBuffers.push(buffer); + return buffer; + } + }]); + + return HtmlMediaSource; +})(_videoJs2['default'].EventTarget); + +exports['default'] = HtmlMediaSource; +module.exports = exports['default']; +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./codec-utils":65,"./virtual-source-buffer":74}],71:[function(require,module,exports){ +/** + * @file remove-cues-from-track.js + */ + +/** + * Remove cues from a track on video.js. + * + * @param {Double} start start of where we should remove the cue + * @param {Double} end end of where the we should remove the cue + * @param {Object} track the text track to remove the cues from + * @private + */ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +var removeCuesFromTrack = function removeCuesFromTrack(start, end, track) { + var i = undefined; + var cue = undefined; + + if (!track) { + return; + } + + i = track.cues.length; + + while (i--) { + cue = track.cues[i]; + + // Remove any overlapping cue + if (cue.startTime <= end && cue.endTime >= start) { + track.removeCue(cue); + } + } +}; + +exports["default"] = removeCuesFromTrack; +module.exports = exports["default"]; +},{}],72:[function(require,module,exports){ +/** + * @file transmuxer-worker.js + */ + +/** + * videojs-contrib-media-sources + * + * Copyright (c) 2015 Brightcove + * All rights reserved. + * + * Handles communication between the browser-world and the mux.js + * transmuxer running inside of a WebWorker by exposing a simple + * message-based interface to a Transmuxer object. + */ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +var _muxJsLibMp4 = require('mux.js/lib/mp4'); + +var _muxJsLibMp42 = _interopRequireDefault(_muxJsLibMp4); + +/** + * Re-emits tranmsuxer events by converting them into messages to the + * world outside the worker. + * + * @param {Object} transmuxer the transmuxer to wire events on + * @private + */ +var wireTransmuxerEvents = function wireTransmuxerEvents(transmuxer) { + transmuxer.on('data', function (segment) { + // transfer ownership of the underlying ArrayBuffer + // instead of doing a copy to save memory + // ArrayBuffers are transferable but generic TypedArrays are not + // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects) + var typedArray = segment.data; + + segment.data = typedArray.buffer; + postMessage({ + action: 'data', + segment: segment, + byteOffset: typedArray.byteOffset, + byteLength: typedArray.byteLength + }, [segment.data]); + }); + + if (transmuxer.captionStream) { + transmuxer.captionStream.on('data', function (caption) { + postMessage({ + action: 'caption', + data: caption + }); + }); + } + + transmuxer.on('done', function (data) { + postMessage({ action: 'done' }); + }); +}; + +/** + * All incoming messages route through this hash. If no function exists + * to handle an incoming message, then we ignore the message. + * + * @class MessageHandlers + * @param {Object} options the options to initialize with + */ + +var MessageHandlers = (function () { + function MessageHandlers(options) { + _classCallCheck(this, MessageHandlers); + + this.options = options || {}; + this.init(); + } + + /** + * Our web wroker interface so that things can talk to mux.js + * that will be running in a web worker. the scope is passed to this by + * webworkify. + * + * @param {Object} self the scope for the web worker + */ + + /** + * initialize our web worker and wire all the events. + */ + + _createClass(MessageHandlers, [{ + key: 'init', + value: function init() { + if (this.transmuxer) { + this.transmuxer.dispose(); + } + this.transmuxer = new _muxJsLibMp42['default'].Transmuxer(this.options); + wireTransmuxerEvents(this.transmuxer); + } + + /** + * Adds data (a ts segment) to the start of the transmuxer pipeline for + * processing. + * + * @param {ArrayBuffer} data data to push into the muxer + */ + }, { + key: 'push', + value: function push(data) { + // Cast array buffer to correct type for transmuxer + var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength); + + this.transmuxer.push(segment); + } + + /** + * Recreate the transmuxer so that the next segment added via `push` + * start with a fresh transmuxer. + */ + }, { + key: 'reset', + value: function reset() { + this.init(); + } + + /** + * Set the value that will be used as the `baseMediaDecodeTime` time for the + * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime` + * set relative to the first based on the PTS values. + * + * @param {Object} data used to set the timestamp offset in the muxer + */ + }, { + key: 'setTimestampOffset', + value: function setTimestampOffset(data) { + var timestampOffset = data.timestampOffset || 0; + + this.transmuxer.setBaseMediaDecodeTime(Math.round(timestampOffset * 90000)); + } + + /** + * Forces the pipeline to finish processing the last segment and emit it's + * results. + * + * @param {Object} data event data, not really used + */ + }, { + key: 'flush', + value: function flush(data) { + this.transmuxer.flush(); + } + }]); + + return MessageHandlers; +})(); + +var Worker = function Worker(self) { + self.onmessage = function (event) { + if (event.data.action === 'init' && event.data.options) { + this.messageHandlers = new MessageHandlers(event.data.options); + return; + } + + if (!this.messageHandlers) { + this.messageHandlers = new MessageHandlers(); + } + + if (event.data && event.data.action && event.data.action !== 'init') { + if (this.messageHandlers[event.data.action]) { + this.messageHandlers[event.data.action](event.data); + } + } + }; +}; + +exports['default'] = function (self) { + return new Worker(self); +}; + +module.exports = exports['default']; +},{"mux.js/lib/mp4":85}],73:[function(require,module,exports){ +(function (global){ +/** + * @file videojs-contrib-media-sources.js + */ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _flashMediaSource = require('./flash-media-source'); + +var _flashMediaSource2 = _interopRequireDefault(_flashMediaSource); + +var _htmlMediaSource = require('./html-media-source'); + +var _htmlMediaSource2 = _interopRequireDefault(_htmlMediaSource); + +var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); + +var _videoJs2 = _interopRequireDefault(_videoJs); + +var urlCount = 0; + +// ------------ +// Media Source +// ------------ + +var defaults = { + // how to determine the MediaSource implementation to use. There + // are three available modes: + // - auto: use native MediaSources where available and Flash + // everywhere else + // - html5: always use native MediaSources + // - flash: always use the Flash MediaSource polyfill + mode: 'auto' +}; + +// store references to the media sources so they can be connected +// to a video element (a swf object) +// TODO: can we store this somewhere local to this module? +_videoJs2['default'].mediaSources = {}; + +/** + * Provide a method for a swf object to notify JS that a + * media source is now open. + * + * @param {String} msObjectURL string referencing the MSE Object URL + * @param {String} swfId the swf id + */ +var open = function open(msObjectURL, swfId) { + var mediaSource = _videoJs2['default'].mediaSources[msObjectURL]; + + if (mediaSource) { + mediaSource.trigger({ type: 'sourceopen', swfId: swfId }); + } else { + throw new Error('Media Source not found (Video.js)'); + } +}; + +/** + * Check to see if the native MediaSource object exists and supports + * an MP4 container with both H.264 video and AAC-LC audio. + * + * @return {Boolean} if native media sources are supported + */ +var supportsNativeMediaSources = function supportsNativeMediaSources() { + return !!window.MediaSource && !!window.MediaSource.isTypeSupported && window.MediaSource.isTypeSupported('video/mp4;codecs="avc1.4d400d,mp4a.40.2"'); +}; + +/** + * An emulation of the MediaSource API so that we can support + * native and non-native functionality such as flash and + * video/mp2t videos. returns an instance of HtmlMediaSource or + * FlashMediaSource depending on what is supported and what options + * are passed in. + * + * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/MediaSource + * @param {Object} options options to use during setup. + */ +var MediaSource = function MediaSource(options) { + var settings = _videoJs2['default'].mergeOptions(defaults, options); + + this.MediaSource = { + open: open, + supportsNativeMediaSources: supportsNativeMediaSources + }; + + // determine whether HTML MediaSources should be used + if (settings.mode === 'html5' || settings.mode === 'auto' && supportsNativeMediaSources()) { + return new _htmlMediaSource2['default'](); + } + + // otherwise, emulate them through the SWF + return new _flashMediaSource2['default'](); +}; + +exports.MediaSource = MediaSource; +MediaSource.open = open; +MediaSource.supportsNativeMediaSources = supportsNativeMediaSources; + +/** + * A wrapper around the native URL for our MSE object + * implementation, this object is exposed under videojs.URL + * + * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/URL + */ +var URL = { + /** + * A wrapper around the native createObjectURL for our objects. + * This function maps a native or emulated mediaSource to a blob + * url so that it can be loaded into video.js + * + * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL + * @param {MediaSource} object the object to create a blob url to + */ + createObjectURL: function createObjectURL(object) { + var objectUrlPrefix = 'blob:vjs-media-source/'; + var url = undefined; + + // use the native MediaSource to generate an object URL + if (object instanceof _htmlMediaSource2['default']) { + url = window.URL.createObjectURL(object.nativeMediaSource_); + object.url_ = url; + return url; + } + // if the object isn't an emulated MediaSource, delegate to the + // native implementation + if (!(object instanceof _flashMediaSource2['default'])) { + url = window.URL.createObjectURL(object); + object.url_ = url; + return url; + } + + // build a URL that can be used to map back to the emulated + // MediaSource + url = objectUrlPrefix + urlCount; + + urlCount++; + + // setup the mapping back to object + _videoJs2['default'].mediaSources[url] = object; + + return url; + } +}; + +exports.URL = URL; +_videoJs2['default'].MediaSource = MediaSource; +_videoJs2['default'].URL = URL; +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./flash-media-source":68,"./html-media-source":70}],74:[function(require,module,exports){ +(function (global){ +/** + * @file virtual-source-buffer.js + */ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); + +var _videoJs2 = _interopRequireDefault(_videoJs); + +var _createTextTracksIfNecessary = require('./create-text-tracks-if-necessary'); + +var _createTextTracksIfNecessary2 = _interopRequireDefault(_createTextTracksIfNecessary); + +var _removeCuesFromTrack = require('./remove-cues-from-track'); + +var _removeCuesFromTrack2 = _interopRequireDefault(_removeCuesFromTrack); + +var _addTextTrackData = require('./add-text-track-data'); + +var _addTextTrackData2 = _interopRequireDefault(_addTextTrackData); + +var _webworkify = require('webworkify'); + +var _webworkify2 = _interopRequireDefault(_webworkify); + +var _transmuxerWorker = require('./transmuxer-worker'); + +var _transmuxerWorker2 = _interopRequireDefault(_transmuxerWorker); + +var _codecUtils = require('./codec-utils'); + +/** + * VirtualSourceBuffers exist so that we can transmux non native formats + * into a native format, but keep the same api as a native source buffer. + * It creates a transmuxer, that works in its own thread (a web worker) and + * that transmuxer muxes the data into a native format. VirtualSourceBuffer will + * then send all of that data to the naive sourcebuffer so that it is + * indestinguishable from a natively supported format. + * + * @param {HtmlMediaSource} mediaSource the parent mediaSource + * @param {Array} codecs array of codecs that we will be dealing with + * @class VirtualSourceBuffer + * @extends video.js.EventTarget + */ + +var VirtualSourceBuffer = (function (_videojs$EventTarget) { + _inherits(VirtualSourceBuffer, _videojs$EventTarget); + + function VirtualSourceBuffer(mediaSource, codecs) { + var _this = this; + + _classCallCheck(this, VirtualSourceBuffer); + + _get(Object.getPrototypeOf(VirtualSourceBuffer.prototype), 'constructor', this).call(this, _videoJs2['default'].EventTarget); + this.timestampOffset_ = 0; + this.pendingBuffers_ = []; + this.bufferUpdating_ = false; + this.mediaSource_ = mediaSource; + this.codecs_ = codecs; + this.audioCodec_ = null; + this.videoCodec_ = null; + this.audioDisabled_ = false; + + var options = { + remux: false + }; + + this.codecs_.forEach(function (codec) { + if ((0, _codecUtils.isAudioCodec)(codec)) { + _this.audioCodec_ = codec; + } else if ((0, _codecUtils.isVideoCodec)(codec)) { + _this.videoCodec_ = codec; + } + }); + + // append muxed segments to their respective native buffers as + // soon as they are available + this.transmuxer_ = (0, _webworkify2['default'])(_transmuxerWorker2['default']); + this.transmuxer_.postMessage({ action: 'init', options: options }); + + this.transmuxer_.onmessage = function (event) { + if (event.data.action === 'data') { + return _this.data_(event); + } + + if (event.data.action === 'done') { + return _this.done_(event); + } + }; + + // this timestampOffset is a property with the side-effect of resetting + // baseMediaDecodeTime in the transmuxer on the setter + Object.defineProperty(this, 'timestampOffset', { + get: function get() { + return this.timestampOffset_; + }, + set: function set(val) { + if (typeof val === 'number' && val >= 0) { + this.timestampOffset_ = val; + + // We have to tell the transmuxer to set the baseMediaDecodeTime to + // the desired timestampOffset for the next segment + this.transmuxer_.postMessage({ + action: 'setTimestampOffset', + timestampOffset: val + }); + } + } + }); + + // setting the append window affects both source buffers + Object.defineProperty(this, 'appendWindowStart', { + get: function get() { + return (this.videoBuffer_ || this.audioBuffer_).appendWindowStart; + }, + set: function set(start) { + if (this.videoBuffer_) { + this.videoBuffer_.appendWindowStart = start; + } + if (this.audioBuffer_) { + this.audioBuffer_.appendWindowStart = start; + } + } + }); + + // this buffer is "updating" if either of its native buffers are + Object.defineProperty(this, 'updating', { + get: function get() { + return !!(this.bufferUpdating_ || !this.audioDisabled_ && this.audioBuffer_ && this.audioBuffer_.updating || this.videoBuffer_ && this.videoBuffer_.updating); + } + }); + + // the buffered property is the intersection of the buffered + // ranges of the native source buffers + Object.defineProperty(this, 'buffered', { + get: function get() { + var start = null; + var end = null; + var arity = 0; + var extents = []; + var ranges = []; + + if (!this.videoBuffer_ && (this.audioDisabled_ || !this.audioBuffer_)) { + return _videoJs2['default'].createTimeRange(); + } + + // Handle the case where we only have one buffer + if (!this.videoBuffer_) { + return this.audioBuffer_.buffered; + } else if (this.audioDisabled_ || !this.audioBuffer_) { + return this.videoBuffer_.buffered; + } + + // Handle the case where there is no buffer data + if ((!this.videoBuffer_ || this.videoBuffer_.buffered.length === 0) && (!this.audioBuffer_ || this.audioBuffer_.buffered.length === 0)) { + return _videoJs2['default'].createTimeRange(); + } + + // Handle the case where we have both buffers and create an + // intersection of the two + var videoBuffered = this.videoBuffer_.buffered; + var audioBuffered = this.audioBuffer_.buffered; + var count = videoBuffered.length; + + // A) Gather up all start and end times + while (count--) { + extents.push({ time: videoBuffered.start(count), type: 'start' }); + extents.push({ time: videoBuffered.end(count), type: 'end' }); + } + count = audioBuffered.length; + while (count--) { + extents.push({ time: audioBuffered.start(count), type: 'start' }); + extents.push({ time: audioBuffered.end(count), type: 'end' }); + } + // B) Sort them by time + extents.sort(function (a, b) { + return a.time - b.time; + }); + + // C) Go along one by one incrementing arity for start and decrementing + // arity for ends + for (count = 0; count < extents.length; count++) { + if (extents[count].type === 'start') { + arity++; + + // D) If arity is ever incremented to 2 we are entering an + // overlapping range + if (arity === 2) { + start = extents[count].time; + } + } else if (extents[count].type === 'end') { + arity--; + + // E) If arity is ever decremented to 1 we leaving an + // overlapping range + if (arity === 1) { + end = extents[count].time; + } + } + + // F) Record overlapping ranges + if (start !== null && end !== null) { + ranges.push([start, end]); + start = null; + end = null; + } + } + + return _videoJs2['default'].createTimeRanges(ranges); + } + }); + } + + /** + * When we get a data event from the transmuxer + * we call this function and handle the data that + * was sent to us + * + * @private + * @param {Event} event the data event from the transmuxer + */ + + _createClass(VirtualSourceBuffer, [{ + key: 'data_', + value: function data_(event) { + var segment = event.data.segment; + + // Cast ArrayBuffer to TypedArray + segment.data = new Uint8Array(segment.data, event.data.byteOffset, event.data.byteLength); + + (0, _createTextTracksIfNecessary2['default'])(this, this.mediaSource_, segment); + + // Add the segments to the pendingBuffers array + this.pendingBuffers_.push(segment); + return; + } + + /** + * When we get a done event from the transmuxer + * we call this function and we process all + * of the pending data that we have been saving in the + * data_ function + * + * @private + * @param {Event} event the done event from the transmuxer + */ + }, { + key: 'done_', + value: function done_(event) { + // All buffers should have been flushed from the muxer + // start processing anything we have received + this.processPendingSegments_(); + return; + } + + /** + * Create our internal native audio/video source buffers and add + * event handlers to them with the following conditions: + * 1. they do not already exist on the mediaSource + * 2. this VSB has a codec for them + * + * @private + */ + }, { + key: 'createRealSourceBuffers_', + value: function createRealSourceBuffers_() { + var _this2 = this; + + var types = ['audio', 'video']; + + types.forEach(function (type) { + // Don't create a SourceBuffer of this type if we don't have a + // codec for it + if (!_this2[type + 'Codec_']) { + return; + } + + // Do nothing if a SourceBuffer of this type already exists + if (_this2[type + 'Buffer_']) { + return; + } + + var buffer = null; + + // If the mediasource already has a SourceBuffer for the codec + // use that + if (_this2.mediaSource_[type + 'Buffer_']) { + buffer = _this2.mediaSource_[type + 'Buffer_']; + } else { + buffer = _this2.mediaSource_.nativeMediaSource_.addSourceBuffer(type + '/mp4;codecs="' + _this2[type + 'Codec_'] + '"'); + _this2.mediaSource_[type + 'Buffer_'] = buffer; + } + + _this2[type + 'Buffer_'] = buffer; + + // Wire up the events to the SourceBuffer + ['update', 'updatestart', 'updateend'].forEach(function (event) { + buffer.addEventListener(event, function () { + // if audio is disabled + if (type === 'audio' && _this2.audioDisabled_) { + return; + } + + var shouldTrigger = types.every(function (t) { + // skip checking audio's updating status if audio + // is not enabled + if (t === 'audio' && _this2.audioDisabled_) { + return true; + } + // if the other type if updating we don't trigger + if (type !== t && _this2[t + 'Buffer_'] && _this2[t + 'Buffer_'].updating) { + return false; + } + return true; + }); + + if (shouldTrigger) { + return _this2.trigger(event); + } + }); + }); + }); + } + + /** + * Emulate the native mediasource function, but our function will + * send all of the proposed segments to the transmuxer so that we + * can transmux them before we append them to our internal + * native source buffers in the correct format. + * + * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer + * @param {Uint8Array} segment the segment to append to the buffer + */ + }, { + key: 'appendBuffer', + value: function appendBuffer(segment) { + // Start the internal "updating" state + this.bufferUpdating_ = true; + + this.transmuxer_.postMessage({ + action: 'push', + // Send the typed-array of data as an ArrayBuffer so that + // it can be sent as a "Transferable" and avoid the costly + // memory copy + data: segment.buffer, + + // To recreate the original typed-array, we need information + // about what portion of the ArrayBuffer it was a view into + byteOffset: segment.byteOffset, + byteLength: segment.byteLength + }, [segment.buffer]); + this.transmuxer_.postMessage({ action: 'flush' }); + } + + /** + * Emulate the native mediasource function and remove parts + * of the buffer from any of our internal buffers that exist + * + * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/remove + * @param {Double} start position to start the remove at + * @param {Double} end position to end the remove at + */ + }, { + key: 'remove', + value: function remove(start, end) { + if (this.videoBuffer_) { + this.videoBuffer_.remove(start, end); + } + if (!this.audioDisabled_ && this.audioBuffer_) { + this.audioBuffer_.remove(start, end); + } + + // Remove Metadata Cues (id3) + (0, _removeCuesFromTrack2['default'])(start, end, this.metadataTrack_); + + // Remove Any Captions + (0, _removeCuesFromTrack2['default'])(start, end, this.inbandTextTrack_); + } + + /** + * Process any segments that the muxer has output + * Concatenate segments together based on type and append them into + * their respective sourceBuffers + * + * @private + */ + }, { + key: 'processPendingSegments_', + value: function processPendingSegments_() { + var sortedSegments = { + video: { + segments: [], + bytes: 0 + }, + audio: { + segments: [], + bytes: 0 + }, + captions: [], + metadata: [] + }; + + // Sort segments into separate video/audio arrays and + // keep track of their total byte lengths + sortedSegments = this.pendingBuffers_.reduce(function (segmentObj, segment) { + var type = segment.type; + var data = segment.data; + + segmentObj[type].segments.push(data); + segmentObj[type].bytes += data.byteLength; + + // Gather any captions into a single array + if (segment.captions) { + segmentObj.captions = segmentObj.captions.concat(segment.captions); + } + + if (segment.info) { + segmentObj[type].info = segment.info; + } + + // Gather any metadata into a single array + if (segment.metadata) { + segmentObj.metadata = segmentObj.metadata.concat(segment.metadata); + } + + return segmentObj; + }, sortedSegments); + + // Create the real source buffers if they don't exist by now since we + // finally are sure what tracks are contained in the source + if (!this.videoBuffer_ && !this.audioBuffer_) { + // Remove any codecs that may have been specified by default but + // are no longer applicable now + if (sortedSegments.video.bytes === 0) { + this.videoCodec_ = null; + } + if (sortedSegments.audio.bytes === 0) { + this.audioCodec_ = null; + } + + this.createRealSourceBuffers_(); + } + + if (sortedSegments.audio.info) { + this.mediaSource_.trigger({ type: 'audioinfo', info: sortedSegments.audio.info }); + } + if (sortedSegments.video.info) { + this.mediaSource_.trigger({ type: 'videoinfo', info: sortedSegments.video.info }); + } + + // Merge multiple video and audio segments into one and append + if (this.videoBuffer_) { + this.concatAndAppendSegments_(sortedSegments.video, this.videoBuffer_); + // TODO: are video tracks the only ones with text tracks? + (0, _addTextTrackData2['default'])(this, sortedSegments.captions, sortedSegments.metadata); + } + if (!this.audioDisabled_ && this.audioBuffer_) { + this.concatAndAppendSegments_(sortedSegments.audio, this.audioBuffer_); + } + + this.pendingBuffers_.length = 0; + + // We are no longer in the internal "updating" state + this.bufferUpdating_ = false; + } + + /** + * Combine all segments into a single Uint8Array and then append them + * to the destination buffer + * + * @param {Object} segmentObj + * @param {SourceBuffer} destinationBuffer native source buffer to append data to + * @private + */ + }, { + key: 'concatAndAppendSegments_', + value: function concatAndAppendSegments_(segmentObj, destinationBuffer) { + var offset = 0; + var tempBuffer = undefined; + + if (segmentObj.bytes) { + tempBuffer = new Uint8Array(segmentObj.bytes); + + // Combine the individual segments into one large typed-array + segmentObj.segments.forEach(function (segment) { + tempBuffer.set(segment, offset); + offset += segment.byteLength; + }); + + destinationBuffer.appendBuffer(tempBuffer); + } + } + + /** + * Emulate the native mediasource function. abort any soureBuffer + * actions and throw out any un-appended data. + * + * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/abort + */ + }, { + key: 'abort', + value: function abort() { + if (this.videoBuffer_) { + this.videoBuffer_.abort(); + } + if (this.audioBuffer_) { + this.audioBuffer_.abort(); + } + if (this.transmuxer_) { + this.transmuxer_.postMessage({ action: 'reset' }); + } + this.pendingBuffers_.length = 0; + this.bufferUpdating_ = false; + } + }]); + + return VirtualSourceBuffer; +})(_videoJs2['default'].EventTarget); + +exports['default'] = VirtualSourceBuffer; +module.exports = exports['default']; +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./add-text-track-data":64,"./codec-utils":65,"./create-text-tracks-if-necessary":66,"./remove-cues-from-track":71,"./transmuxer-worker":72,"webworkify":90}],75:[function(require,module,exports){ +/** + * mux.js + * + * Copyright (c) 2016 Brightcove + * All rights reserved. + * + * A stream-based aac to mp4 converter. This utility can be used to + * deliver mp4s to a SourceBuffer on platforms that support native + * Media Source Extensions. + */ +'use strict'; +var Stream = require('../utils/stream.js'); + +// Constants +var AacStream; + +/** + * Splits an incoming stream of binary data into ADTS and ID3 Frames. + */ + +AacStream = function() { + var + everything = new Uint8Array(), + receivedTimeStamp = false, + timeStamp = 0; + + AacStream.prototype.init.call(this); + + this.setTimestamp = function (timestamp) { + timeStamp = timestamp; + }; + + this.parseId3TagSize = function(header, byteIndex) { + var + returnSize = (header[byteIndex + 6] << 21) | + (header[byteIndex + 7] << 14) | + (header[byteIndex + 8] << 7) | + (header[byteIndex + 9]), + flags = header[byteIndex + 5], + footerPresent = (flags & 16) >> 4; + + if (footerPresent) { + return returnSize + 20; + } + return returnSize + 10; + }; + + this.parseAdtsSize = function(header, byteIndex) { + var + lowThree = (header[byteIndex + 5] & 0xE0) >> 5, + middle = header[byteIndex + 4] << 3, + highTwo = header[byteIndex + 3] & 0x3 << 11; + + return (highTwo | middle) | lowThree; + }; + + this.push = function(bytes) { + var + frameSize = 0, + byteIndex = 0, + bytesLeft, + chunk, + packet, + tempLength; + + // If there are bytes remaining from the last segment, prepend them to the + // bytes that were pushed in + if (everything.length) { + tempLength = everything.length; + everything = new Uint8Array(bytes.byteLength + tempLength); + everything.set(everything.subarray(0, tempLength)); + everything.set(bytes, tempLength); + } else { + everything = bytes; + } + + while (everything.length - byteIndex >= 3) { + if ((everything[byteIndex] === 'I'.charCodeAt(0)) && + (everything[byteIndex + 1] === 'D'.charCodeAt(0)) && + (everything[byteIndex + 2] === '3'.charCodeAt(0))) { + + // Exit early because we don't have enough to parse + // the ID3 tag header + if (everything.length - byteIndex < 10) { + break; + } + + // check framesize + frameSize = this.parseId3TagSize(everything, byteIndex); + + // Exit early if we don't have enough in the buffer + // to emit a full packet + if (frameSize > everything.length) { + break; + } + chunk = { + type: 'timed-metadata', + data: everything.subarray(byteIndex, byteIndex + frameSize) + }; + this.trigger('data', chunk); + byteIndex += frameSize; + continue; + } else if ((everything[byteIndex] & 0xff === 0xff) && + ((everything[byteIndex + 1] & 0xf0) === 0xf0)) { + + // Exit early because we don't have enough to parse + // the ADTS frame header + if (everything.length - byteIndex < 7) { + break; + } + + frameSize = this.parseAdtsSize(everything, byteIndex); + + // Exit early if we don't have enough in the buffer + // to emit a full packet + if (frameSize > everything.length) { + break; + } + + packet = { + type: 'audio', + data: everything.subarray(byteIndex, byteIndex + frameSize), + pts: timeStamp, + dts: timeStamp, + }; + this.trigger('data', packet); + byteIndex += frameSize; + continue; + } + byteIndex++; + } + bytesLeft = everything.length - byteIndex; + + if (bytesLeft > 0) { + everything = everything.subarray(byteIndex); + } else { + everything = new Uint8Array(); + } + }; +}; + +AacStream.prototype = new Stream(); + + + +module.exports = AacStream; + +},{"../utils/stream.js":89}],76:[function(require,module,exports){ +'use strict'; + +var Stream = require('../utils/stream.js'); + +var AdtsStream; + +var + ADTS_SAMPLING_FREQUENCIES = [ + 96000, + 88200, + 64000, + 48000, + 44100, + 32000, + 24000, + 22050, + 16000, + 12000, + 11025, + 8000, + 7350 + ]; + +/* + * Accepts a ElementaryStream and emits data events with parsed + * AAC Audio Frames of the individual packets. Input audio in ADTS + * format is unpacked and re-emitted as AAC frames. + * + * @see http://wiki.multimedia.cx/index.php?title=ADTS + * @see http://wiki.multimedia.cx/?title=Understanding_AAC + */ +AdtsStream = function() { + var self, buffer; + + AdtsStream.prototype.init.call(this); + + self = this; + + this.push = function(packet) { + var + i = 0, + frameNum = 0, + frameLength, + protectionSkipBytes, + frameEnd, + oldBuffer, + numFrames, + sampleCount, + adtsFrameDuration; + + if (packet.type !== 'audio') { + // ignore non-audio data + return; + } + + // Prepend any data in the buffer to the input data so that we can parse + // aac frames the cross a PES packet boundary + if (buffer) { + oldBuffer = buffer; + buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength); + buffer.set(oldBuffer); + buffer.set(packet.data, oldBuffer.byteLength); + } else { + buffer = packet.data; + } + + // unpack any ADTS frames which have been fully received + // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS + while (i + 5 < buffer.length) { + + // Loook for the start of an ADTS header.. + if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) { + // If a valid header was not found, jump one forward and attempt to + // find a valid ADTS header starting at the next byte + i++; + continue; + } + + // The protection skip bit tells us if we have 2 bytes of CRC data at the + // end of the ADTS header + protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; + + // Frame length is a 13 bit integer starting 16 bits from the + // end of the sync sequence + frameLength = ((buffer[i + 3] & 0x03) << 11) | + (buffer[i + 4] << 3) | + ((buffer[i + 5] & 0xe0) >> 5); + + sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024; + adtsFrameDuration = (sampleCount * 90000) / + ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2]; + + frameEnd = i + frameLength; + + // If we don't have enough data to actually finish this ADTS frame, return + // and wait for more data + if (buffer.byteLength < frameEnd) { + return; + } + + // Otherwise, deliver the complete AAC frame + this.trigger('data', { + pts: packet.pts + (frameNum * adtsFrameDuration), + dts: packet.dts + (frameNum * adtsFrameDuration), + sampleCount: sampleCount, + audioobjecttype: ((buffer[i + 2] >>> 6) & 0x03) + 1, + channelcount: ((buffer[i + 2] & 1) << 2) | + ((buffer[i + 3] & 0xc0) >>> 6), + samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2], + samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2, + // assume ISO/IEC 14496-12 AudioSampleEntry default of 16 + samplesize: 16, + data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd) + }); + + // If the buffer is empty, clear it and return + if (buffer.byteLength === frameEnd) { + buffer = undefined; + return; + } + + frameNum++; + + // Remove the finished frame from the buffer and start the process again + buffer = buffer.subarray(frameEnd); + } + }; + this.flush = function() { + this.trigger('done'); + }; +}; + +AdtsStream.prototype = new Stream(); + +module.exports = AdtsStream; + +},{"../utils/stream.js":89}],77:[function(require,module,exports){ +'use strict'; + +var Stream = require('../utils/stream.js'); +var ExpGolomb = require('../utils/exp-golomb.js'); + +var H264Stream, NalByteStream; + +/** + * Accepts a NAL unit byte stream and unpacks the embedded NAL units. + */ +NalByteStream = function() { + var + syncPoint = 0, + i, + buffer; + NalByteStream.prototype.init.call(this); + + this.push = function(data) { + var swapBuffer; + + if (!buffer) { + buffer = data.data; + } else { + swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength); + swapBuffer.set(buffer); + swapBuffer.set(data.data, buffer.byteLength); + buffer = swapBuffer; + } + + // Rec. ITU-T H.264, Annex B + // scan for NAL unit boundaries + + // a match looks like this: + // 0 0 1 .. NAL .. 0 0 1 + // ^ sync point ^ i + // or this: + // 0 0 1 .. NAL .. 0 0 0 + // ^ sync point ^ i + + // advance the sync point to a NAL start, if necessary + for (; syncPoint < buffer.byteLength - 3; syncPoint++) { + if (buffer[syncPoint + 2] === 1) { + // the sync point is properly aligned + i = syncPoint + 5; + break; + } + } + + while (i < buffer.byteLength) { + // look at the current byte to determine if we've hit the end of + // a NAL unit boundary + switch (buffer[i]) { + case 0: + // skip past non-sync sequences + if (buffer[i - 1] !== 0) { + i += 2; + break; + } else if (buffer[i - 2] !== 0) { + i++; + break; + } + + // deliver the NAL unit if it isn't empty + if (syncPoint + 3 !== i - 2) { + this.trigger('data', buffer.subarray(syncPoint + 3, i - 2)); + } + + // drop trailing zeroes + do { + i++; + } while (buffer[i] !== 1 && i < buffer.length); + syncPoint = i - 2; + i += 3; + break; + case 1: + // skip past non-sync sequences + if (buffer[i - 1] !== 0 || + buffer[i - 2] !== 0) { + i += 3; + break; + } + + // deliver the NAL unit + this.trigger('data', buffer.subarray(syncPoint + 3, i - 2)); + syncPoint = i - 2; + i += 3; + break; + default: + // the current byte isn't a one or zero, so it cannot be part + // of a sync sequence + i += 3; + break; + } + } + // filter out the NAL units that were delivered + buffer = buffer.subarray(syncPoint); + i -= syncPoint; + syncPoint = 0; + }; + + this.flush = function() { + // deliver the last buffered NAL unit + if (buffer && buffer.byteLength > 3) { + this.trigger('data', buffer.subarray(syncPoint + 3)); + } + // reset the stream state + buffer = null; + syncPoint = 0; + this.trigger('done'); + }; +}; +NalByteStream.prototype = new Stream(); + +/** + * Accepts input from a ElementaryStream and produces H.264 NAL unit data + * events. + */ +H264Stream = function() { + var + nalByteStream = new NalByteStream(), + self, + trackId, + currentPts, + currentDts, + + discardEmulationPreventionBytes, + readSequenceParameterSet, + skipScalingList; + + H264Stream.prototype.init.call(this); + self = this; + + this.push = function(packet) { + if (packet.type !== 'video') { + return; + } + trackId = packet.trackId; + currentPts = packet.pts; + currentDts = packet.dts; + + nalByteStream.push(packet); + }; + + nalByteStream.on('data', function(data) { + var + event = { + trackId: trackId, + pts: currentPts, + dts: currentDts, + data: data + }; + + switch (data[0] & 0x1f) { + case 0x05: + event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr'; + break; + case 0x06: + event.nalUnitType = 'sei_rbsp'; + event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1)); + break; + case 0x07: + event.nalUnitType = 'seq_parameter_set_rbsp'; + event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1)); + event.config = readSequenceParameterSet(event.escapedRBSP); + break; + case 0x08: + event.nalUnitType = 'pic_parameter_set_rbsp'; + break; + case 0x09: + event.nalUnitType = 'access_unit_delimiter_rbsp'; + break; + + default: + break; + } + self.trigger('data', event); + }); + nalByteStream.on('done', function() { + self.trigger('done'); + }); + + this.flush = function() { + nalByteStream.flush(); + }; + + /** + * Advance the ExpGolomb decoder past a scaling list. The scaling + * list is optionally transmitted as part of a sequence parameter + * set and is not relevant to transmuxing. + * @param count {number} the number of entries in this scaling list + * @param expGolombDecoder {object} an ExpGolomb pointed to the + * start of a scaling list + * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1 + */ + skipScalingList = function(count, expGolombDecoder) { + var + lastScale = 8, + nextScale = 8, + j, + deltaScale; + + for (j = 0; j < count; j++) { + if (nextScale !== 0) { + deltaScale = expGolombDecoder.readExpGolomb(); + nextScale = (lastScale + deltaScale + 256) % 256; + } + + lastScale = (nextScale === 0) ? lastScale : nextScale; + } + }; + + /** + * Expunge any "Emulation Prevention" bytes from a "Raw Byte + * Sequence Payload" + * @param data {Uint8Array} the bytes of a RBSP from a NAL + * unit + * @return {Uint8Array} the RBSP without any Emulation + * Prevention Bytes + */ + discardEmulationPreventionBytes = function(data) { + var + length = data.byteLength, + emulationPreventionBytesPositions = [], + i = 1, + newLength, newData; + + // Find all `Emulation Prevention Bytes` + while (i < length - 2) { + if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) { + emulationPreventionBytesPositions.push(i + 2); + i += 2; + } else { + i++; + } + } + + // If no Emulation Prevention Bytes were found just return the original + // array + if (emulationPreventionBytesPositions.length === 0) { + return data; + } + + // Create a new array to hold the NAL unit data + newLength = length - emulationPreventionBytesPositions.length; + newData = new Uint8Array(newLength); + var sourceIndex = 0; + + for (i = 0; i < newLength; sourceIndex++, i++) { + if (sourceIndex === emulationPreventionBytesPositions[0]) { + // Skip this byte + sourceIndex++; + // Remove this position index + emulationPreventionBytesPositions.shift(); + } + newData[i] = data[sourceIndex]; + } + + return newData; + }; + + /** + * Read a sequence parameter set and return some interesting video + * properties. A sequence parameter set is the H264 metadata that + * describes the properties of upcoming video frames. + * @param data {Uint8Array} the bytes of a sequence parameter set + * @return {object} an object with configuration parsed from the + * sequence parameter set, including the dimensions of the + * associated video frames. + */ + readSequenceParameterSet = function(data) { + var + frameCropLeftOffset = 0, + frameCropRightOffset = 0, + frameCropTopOffset = 0, + frameCropBottomOffset = 0, + sarScale = 1, + expGolombDecoder, profileIdc, levelIdc, profileCompatibility, + chromaFormatIdc, picOrderCntType, + numRefFramesInPicOrderCntCycle, picWidthInMbsMinus1, + picHeightInMapUnitsMinus1, + frameMbsOnlyFlag, + scalingListCount, + sarRatio, + aspectRatioIdc, + i; + + expGolombDecoder = new ExpGolomb(data); + profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc + profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag + levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8) + expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id + + // some profiles have more optional data we don't need + if (profileIdc === 100 || + profileIdc === 110 || + profileIdc === 122 || + profileIdc === 244 || + profileIdc === 44 || + profileIdc === 83 || + profileIdc === 86 || + profileIdc === 118 || + profileIdc === 128 || + profileIdc === 138 || + profileIdc === 139 || + profileIdc === 134) { + chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb(); + if (chromaFormatIdc === 3) { + expGolombDecoder.skipBits(1); // separate_colour_plane_flag + } + expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8 + expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8 + expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag + if (expGolombDecoder.readBoolean()) { // seq_scaling_matrix_present_flag + scalingListCount = (chromaFormatIdc !== 3) ? 8 : 12; + for (i = 0; i < scalingListCount; i++) { + if (expGolombDecoder.readBoolean()) { // seq_scaling_list_present_flag[ i ] + if (i < 6) { + skipScalingList(16, expGolombDecoder); + } else { + skipScalingList(64, expGolombDecoder); + } + } + } + } + } + + expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4 + picOrderCntType = expGolombDecoder.readUnsignedExpGolomb(); + + if (picOrderCntType === 0) { + expGolombDecoder.readUnsignedExpGolomb(); //log2_max_pic_order_cnt_lsb_minus4 + } else if (picOrderCntType === 1) { + expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag + expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic + expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field + numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb(); + for(i = 0; i < numRefFramesInPicOrderCntCycle; i++) { + expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ] + } + } + + expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames + expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag + + picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb(); + picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb(); + + frameMbsOnlyFlag = expGolombDecoder.readBits(1); + if (frameMbsOnlyFlag === 0) { + expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag + } + + expGolombDecoder.skipBits(1); // direct_8x8_inference_flag + if (expGolombDecoder.readBoolean()) { // frame_cropping_flag + frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb(); + frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb(); + frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb(); + frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb(); + } + if (expGolombDecoder.readBoolean()) { + // vui_parameters_present_flag + if (expGolombDecoder.readBoolean()) { + // aspect_ratio_info_present_flag + aspectRatioIdc = expGolombDecoder.readUnsignedByte(); + switch (aspectRatioIdc) { + case 1: sarRatio = [1,1]; break; + case 2: sarRatio = [12,11]; break; + case 3: sarRatio = [10,11]; break; + case 4: sarRatio = [16,11]; break; + case 5: sarRatio = [40,33]; break; + case 6: sarRatio = [24,11]; break; + case 7: sarRatio = [20,11]; break; + case 8: sarRatio = [32,11]; break; + case 9: sarRatio = [80,33]; break; + case 10: sarRatio = [18,11]; break; + case 11: sarRatio = [15,11]; break; + case 12: sarRatio = [64,33]; break; + case 13: sarRatio = [160,99]; break; + case 14: sarRatio = [4,3]; break; + case 15: sarRatio = [3,2]; break; + case 16: sarRatio = [2,1]; break; + case 255: { + sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | + expGolombDecoder.readUnsignedByte(), + expGolombDecoder.readUnsignedByte() << 8 | + expGolombDecoder.readUnsignedByte() ]; + break; + } + } + if (sarRatio) { + sarScale = sarRatio[0] / sarRatio[1]; + } + } + } + return { + profileIdc: profileIdc, + levelIdc: levelIdc, + profileCompatibility: profileCompatibility, + width: Math.ceil((((picWidthInMbsMinus1 + 1) * 16) - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale), + height: ((2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16) - (frameCropTopOffset * 2) - (frameCropBottomOffset * 2) + }; + }; + +}; +H264Stream.prototype = new Stream(); + +module.exports = { + H264Stream: H264Stream, + NalByteStream: NalByteStream, +}; + +},{"../utils/exp-golomb.js":88,"../utils/stream.js":89}],78:[function(require,module,exports){ +/** + * An object that stores the bytes of an FLV tag and methods for + * querying and manipulating that data. + * @see http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf + */ +'use strict'; + +var FlvTag; + +// (type:uint, extraData:Boolean = false) extends ByteArray +FlvTag = function(type, extraData) { + var + // Counter if this is a metadata tag, nal start marker if this is a video + // tag. unused if this is an audio tag + adHoc = 0, // :uint + + // The default size is 16kb but this is not enough to hold iframe + // data and the resizing algorithm costs a bit so we create a larger + // starting buffer for video tags + bufferStartSize = 16384, + + // checks whether the FLV tag has enough capacity to accept the proposed + // write and re-allocates the internal buffers if necessary + prepareWrite = function(flv, count) { + var + bytes, + minLength = flv.position + count; + if (minLength < flv.bytes.byteLength) { + // there's enough capacity so do nothing + return; + } + + // allocate a new buffer and copy over the data that will not be modified + bytes = new Uint8Array(minLength * 2); + bytes.set(flv.bytes.subarray(0, flv.position), 0); + flv.bytes = bytes; + flv.view = new DataView(flv.bytes.buffer); + }, + + // commonly used metadata properties + widthBytes = FlvTag.widthBytes || new Uint8Array('width'.length), + heightBytes = FlvTag.heightBytes || new Uint8Array('height'.length), + videocodecidBytes = FlvTag.videocodecidBytes || new Uint8Array('videocodecid'.length), + i; + + if (!FlvTag.widthBytes) { + // calculating the bytes of common metadata names ahead of time makes the + // corresponding writes faster because we don't have to loop over the + // characters + // re-test with test/perf.html if you're planning on changing this + for (i = 0; i < 'width'.length; i++) { + widthBytes[i] = 'width'.charCodeAt(i); + } + for (i = 0; i < 'height'.length; i++) { + heightBytes[i] = 'height'.charCodeAt(i); + } + for (i = 0; i < 'videocodecid'.length; i++) { + videocodecidBytes[i] = 'videocodecid'.charCodeAt(i); + } + + FlvTag.widthBytes = widthBytes; + FlvTag.heightBytes = heightBytes; + FlvTag.videocodecidBytes = videocodecidBytes; + } + + this.keyFrame = false; // :Boolean + + switch(type) { + case FlvTag.VIDEO_TAG: + this.length = 16; + // Start the buffer at 256k + bufferStartSize *= 6; + break; + case FlvTag.AUDIO_TAG: + this.length = 13; + this.keyFrame = true; + break; + case FlvTag.METADATA_TAG: + this.length = 29; + this.keyFrame = true; + break; + default: + throw("Error Unknown TagType"); + } + + this.bytes = new Uint8Array(bufferStartSize); + this.view = new DataView(this.bytes.buffer); + this.bytes[0] = type; + this.position = this.length; + this.keyFrame = extraData; // Defaults to false + + // presentation timestamp + this.pts = 0; + // decoder timestamp + this.dts = 0; + + // ByteArray#writeBytes(bytes:ByteArray, offset:uint = 0, length:uint = 0) + this.writeBytes = function(bytes, offset, length) { + var + start = offset || 0, + end; + length = length || bytes.byteLength; + end = start + length; + + prepareWrite(this, length); + this.bytes.set(bytes.subarray(start, end), this.position); + + this.position += length; + this.length = Math.max(this.length, this.position); + }; + + // ByteArray#writeByte(value:int):void + this.writeByte = function(byte) { + prepareWrite(this, 1); + this.bytes[this.position] = byte; + this.position++; + this.length = Math.max(this.length, this.position); + }; + + // ByteArray#writeShort(value:int):void + this.writeShort = function(short) { + prepareWrite(this, 2); + this.view.setUint16(this.position, short); + this.position += 2; + this.length = Math.max(this.length, this.position); + }; + + // Negative index into array + // (pos:uint):int + this.negIndex = function(pos) { + return this.bytes[this.length - pos]; + }; + + // The functions below ONLY work when this[0] == VIDEO_TAG. + // We are not going to check for that because we dont want the overhead + // (nal:ByteArray = null):int + this.nalUnitSize = function() { + if (adHoc === 0) { + return 0; + } + + return this.length - (adHoc + 4); + }; + + this.startNalUnit = function() { + // remember position and add 4 bytes + if (adHoc > 0) { + throw new Error("Attempted to create new NAL wihout closing the old one"); + } + + // reserve 4 bytes for nal unit size + adHoc = this.length; + this.length += 4; + this.position = this.length; + }; + + // (nal:ByteArray = null):void + this.endNalUnit = function(nalContainer) { + var + nalStart, // :uint + nalLength; // :uint + + // Rewind to the marker and write the size + if (this.length === adHoc + 4) { + // we started a nal unit, but didnt write one, so roll back the 4 byte size value + this.length -= 4; + } else if (adHoc > 0) { + nalStart = adHoc + 4; + nalLength = this.length - nalStart; + + this.position = adHoc; + this.view.setUint32(this.position, nalLength); + this.position = this.length; + + if (nalContainer) { + // Add the tag to the NAL unit + nalContainer.push(this.bytes.subarray(nalStart, nalStart + nalLength)); + } + } + + adHoc = 0; + }; + + /** + * Write out a 64-bit floating point valued metadata property. This method is + * called frequently during a typical parse and needs to be fast. + */ + // (key:String, val:Number):void + this.writeMetaDataDouble = function(key, val) { + var i; + prepareWrite(this, 2 + key.length + 9); + + // write size of property name + this.view.setUint16(this.position, key.length); + this.position += 2; + + // this next part looks terrible but it improves parser throughput by + // 10kB/s in my testing + + // write property name + if (key === 'width') { + this.bytes.set(widthBytes, this.position); + this.position += 5; + } else if (key === 'height') { + this.bytes.set(heightBytes, this.position); + this.position += 6; + } else if (key === 'videocodecid') { + this.bytes.set(videocodecidBytes, this.position); + this.position += 12; + } else { + for (i = 0; i < key.length; i++) { + this.bytes[this.position] = key.charCodeAt(i); + this.position++; + } + } + + // skip null byte + this.position++; + + // write property value + this.view.setFloat64(this.position, val); + this.position += 8; + + // update flv tag length + this.length = Math.max(this.length, this.position); + ++adHoc; + }; + + // (key:String, val:Boolean):void + this.writeMetaDataBoolean = function(key, val) { + var i; + prepareWrite(this, 2); + this.view.setUint16(this.position, key.length); + this.position += 2; + for (i = 0; i < key.length; i++) { + // if key.charCodeAt(i) >= 255, handle error + prepareWrite(this, 1); + this.bytes[this.position] = key.charCodeAt(i); + this.position++; + } + prepareWrite(this, 2); + this.view.setUint8(this.position, 0x01); + this.position++; + this.view.setUint8(this.position, val ? 0x01 : 0x00); + this.position++; + this.length = Math.max(this.length, this.position); + ++adHoc; + }; + + // ():ByteArray + this.finalize = function() { + var + dtsDelta, // :int + len; // :int + + switch(this.bytes[0]) { + // Video Data + case FlvTag.VIDEO_TAG: + this.bytes[11] = ((this.keyFrame || extraData) ? 0x10 : 0x20 ) | 0x07; // We only support AVC, 1 = key frame (for AVC, a seekable frame), 2 = inter frame (for AVC, a non-seekable frame) + this.bytes[12] = extraData ? 0x00 : 0x01; + + dtsDelta = this.pts - this.dts; + this.bytes[13] = (dtsDelta & 0x00FF0000) >>> 16; + this.bytes[14] = (dtsDelta & 0x0000FF00) >>> 8; + this.bytes[15] = (dtsDelta & 0x000000FF) >>> 0; + break; + + case FlvTag.AUDIO_TAG: + this.bytes[11] = 0xAF; // 44 kHz, 16-bit stereo + this.bytes[12] = extraData ? 0x00 : 0x01; + break; + + case FlvTag.METADATA_TAG: + this.position = 11; + this.view.setUint8(this.position, 0x02); // String type + this.position++; + this.view.setUint16(this.position, 0x0A); // 10 Bytes + this.position += 2; + // set "onMetaData" + this.bytes.set([0x6f, 0x6e, 0x4d, 0x65, + 0x74, 0x61, 0x44, 0x61, + 0x74, 0x61], this.position); + this.position += 10; + this.bytes[this.position] = 0x08; // Array type + this.position++; + this.view.setUint32(this.position, adHoc); + this.position = this.length; + this.bytes.set([0, 0, 9], this.position); + this.position += 3; // End Data Tag + this.length = this.position; + break; + } + + len = this.length - 11; + + // write the DataSize field + this.bytes[ 1] = (len & 0x00FF0000) >>> 16; + this.bytes[ 2] = (len & 0x0000FF00) >>> 8; + this.bytes[ 3] = (len & 0x000000FF) >>> 0; + // write the Timestamp + this.bytes[ 4] = (this.dts & 0x00FF0000) >>> 16; + this.bytes[ 5] = (this.dts & 0x0000FF00) >>> 8; + this.bytes[ 6] = (this.dts & 0x000000FF) >>> 0; + this.bytes[ 7] = (this.dts & 0xFF000000) >>> 24; + // write the StreamID + this.bytes[ 8] = 0; + this.bytes[ 9] = 0; + this.bytes[10] = 0; + + // Sometimes we're at the end of the view and have one slot to write a + // uint32, so, prepareWrite of count 4, since, view is uint8 + prepareWrite(this, 4); + this.view.setUint32(this.length, this.length); + this.length += 4; + this.position += 4; + + // trim down the byte buffer to what is actually being used + this.bytes = this.bytes.subarray(0, this.length); + this.frameTime = FlvTag.frameTime(this.bytes); + // if bytes.bytelength isn't equal to this.length, handle error + return this; + }; +}; + +FlvTag.AUDIO_TAG = 0x08; // == 8, :uint +FlvTag.VIDEO_TAG = 0x09; // == 9, :uint +FlvTag.METADATA_TAG = 0x12; // == 18, :uint + +// (tag:ByteArray):Boolean { +FlvTag.isAudioFrame = function(tag) { + return FlvTag.AUDIO_TAG === tag[0]; +}; + +// (tag:ByteArray):Boolean { +FlvTag.isVideoFrame = function(tag) { + return FlvTag.VIDEO_TAG === tag[0]; +}; + +// (tag:ByteArray):Boolean { +FlvTag.isMetaData = function(tag) { + return FlvTag.METADATA_TAG === tag[0]; +}; + +// (tag:ByteArray):Boolean { +FlvTag.isKeyFrame = function(tag) { + if (FlvTag.isVideoFrame(tag)) { + return tag[11] === 0x17; + } + + if (FlvTag.isAudioFrame(tag)) { + return true; + } + + if (FlvTag.isMetaData(tag)) { + return true; + } + + return false; +}; + +// (tag:ByteArray):uint { +FlvTag.frameTime = function(tag) { + var pts = tag[ 4] << 16; // :uint + pts |= tag[ 5] << 8; + pts |= tag[ 6] << 0; + pts |= tag[ 7] << 24; + return pts; +}; + +module.exports = FlvTag; + +},{}],79:[function(require,module,exports){ +module.exports = { + tag: require('./flv-tag'), + Transmuxer: require('./transmuxer') +}; + +},{"./flv-tag":78,"./transmuxer":80}],80:[function(require,module,exports){ +'use strict'; + +var Stream = require('../utils/stream.js'); +var FlvTag = require('./flv-tag.js'); +var m2ts = require('../m2ts/m2ts.js'); +var AdtsStream = require('../codecs/adts.js'); +var H264Stream = require('../codecs/h264').H264Stream; + +var + MetadataStream, + Transmuxer, + VideoSegmentStream, + AudioSegmentStream, + CoalesceStream, + collectTimelineInfo, + metaDataTag, + extraDataTag; + +/** + * Store information about the start and end of the tracka and the + * duration for each frame/sample we process in order to calculate + * the baseMediaDecodeTime + */ +collectTimelineInfo = function (track, data) { + if (typeof data.pts === 'number') { + if (track.timelineStartInfo.pts === undefined) { + track.timelineStartInfo.pts = data.pts; + } else { + track.timelineStartInfo.pts = + Math.min(track.timelineStartInfo.pts, data.pts); + } + } + + if (typeof data.dts === 'number') { + if (track.timelineStartInfo.dts === undefined) { + track.timelineStartInfo.dts = data.dts; + } else { + track.timelineStartInfo.dts = + Math.min(track.timelineStartInfo.dts, data.dts); + } + } +}; + +metaDataTag = function(track, pts) { + var + tag = new FlvTag(FlvTag.METADATA_TAG); // :FlvTag + + tag.dts = pts; + tag.pts = pts; + + tag.writeMetaDataDouble("videocodecid", 7); + tag.writeMetaDataDouble("width", track.width); + tag.writeMetaDataDouble("height", track.height); + + return tag; +}; + +extraDataTag = function(track, pts) { + var + i, + tag = new FlvTag(FlvTag.VIDEO_TAG, true); + + tag.dts = pts; + tag.pts = pts; + + tag.writeByte(0x01);// version + tag.writeByte(track.profileIdc);// profile + tag.writeByte(track.profileCompatibility);// compatibility + tag.writeByte(track.levelIdc);// level + tag.writeByte(0xFC | 0x03); // reserved (6 bits), NULA length size - 1 (2 bits) + tag.writeByte(0xE0 | 0x01 ); // reserved (3 bits), num of SPS (5 bits) + tag.writeShort( track.sps[0].length ); // data of SPS + tag.writeBytes( track.sps[0] ); // SPS + + tag.writeByte(track.pps.length); // num of PPS (will there ever be more that 1 PPS?) + for (i = 0 ; i < track.pps.length ; ++i) { + tag.writeShort(track.pps[i].length); // 2 bytes for length of PPS + tag.writeBytes(track.pps[i]); // data of PPS + } + + return tag; +}; + +/** + * Constructs a single-track, media segment from AAC data + * events. The output of this stream can be fed to flash. + */ +AudioSegmentStream = function(track) { + var + adtsFrames = [], + adtsFramesLength = 0, + sequenceNumber = 0, + earliestAllowedDts = 0, + oldExtraData; + + AudioSegmentStream.prototype.init.call(this); + + this.push = function(data) { + collectTimelineInfo(track, data); + + if (track && track.channelcount === undefined) { + track.audioobjecttype = data.audioobjecttype; + track.channelcount = data.channelcount; + track.samplerate = data.samplerate; + track.samplingfrequencyindex = data.samplingfrequencyindex; + track.samplesize = data.samplesize; + track.extraData = (track.audioobjecttype << 11) | + (track.samplingfrequencyindex << 7) | + (track.channelcount << 3); + } + + data.pts = Math.round(data.pts / 90); + data.dts = Math.round(data.dts / 90); + + // buffer audio data until end() is called + adtsFrames.push(data); + }; + + this.flush = function() { + var currentFrame, adtsFrame, deltaDts,lastMetaPts, tags = []; + // return early if no audio data has been observed + if (adtsFrames.length === 0) { + this.trigger('done'); + return; + } + + lastMetaPts = -Infinity; + + while (adtsFrames.length) { + currentFrame = adtsFrames.shift(); + + // write out metadata tags every 1 second so that the decoder + // is re-initialized quickly after seeking into a different + // audio configuration + if (track.extraData !== oldExtraData || currentFrame.pts - lastMetaPts >= 1000) { + adtsFrame = new FlvTag(FlvTag.METADATA_TAG); + adtsFrame.pts = currentFrame.pts; + adtsFrame.dts = currentFrame.dts; + + // AAC is always 10 + adtsFrame.writeMetaDataDouble("audiocodecid", 10); + adtsFrame.writeMetaDataBoolean("stereo", 2 === track.channelcount); + adtsFrame.writeMetaDataDouble ("audiosamplerate", track.samplerate); + // Is AAC always 16 bit? + adtsFrame.writeMetaDataDouble ("audiosamplesize", 16); + + tags.push(adtsFrame); + + oldExtraData = track.extraData; + + adtsFrame = new FlvTag(FlvTag.AUDIO_TAG, true); + // For audio, DTS is always the same as PTS. We want to set the DTS + // however so we can compare with video DTS to determine approximate + // packet order + adtsFrame.pts = currentFrame.pts; + adtsFrame.dts = currentFrame.dts; + + adtsFrame.view.setUint16(adtsFrame.position, track.extraData); + adtsFrame.position += 2; + adtsFrame.length = Math.max(adtsFrame.length, adtsFrame.position); + + tags.push(adtsFrame); + + lastMetaPts = currentFrame.pts; + } + adtsFrame = new FlvTag(FlvTag.AUDIO_TAG); + adtsFrame.pts = currentFrame.pts; + adtsFrame.dts = currentFrame.dts; + + adtsFrame.writeBytes(currentFrame.data); + + tags.push(adtsFrame); + } + + oldExtraData = null; + this.trigger('data', {track: track, tags: tags}); + + this.trigger('done'); + }; +}; +AudioSegmentStream.prototype = new Stream(); + +/** + * Store FlvTags for the h264 stream + * @param track {object} track metadata configuration + */ +VideoSegmentStream = function(track) { + var + sequenceNumber = 0, + nalUnits = [], + nalUnitsLength = 0, + config, + h264Frame; + VideoSegmentStream.prototype.init.call(this); + + this.finishFrame = function(tags, frame) { + if (!frame) { + return; + } + // Check if keyframe and the length of tags. + // This makes sure we write metadata on the first frame of a segment. + if (config && track && track.newMetadata && + (frame.keyFrame || tags.length === 0)) { + // Push extra data on every IDR frame in case we did a stream change + seek + tags.push(metaDataTag(config, frame.pts)); + tags.push(extraDataTag(track, frame.pts)); + track.newMetadata = false; + } + + frame.endNalUnit(); + tags.push(frame); + }; + + this.push = function(data) { + collectTimelineInfo(track, data); + + data.pts = Math.round(data.pts / 90); + data.dts = Math.round(data.dts / 90); + + // buffer video until flush() is called + nalUnits.push(data); + }; + + this.flush = function() { + var + currentNal, + tags = []; + + // Throw away nalUnits at the start of the byte stream until we find + // the first AUD + while (nalUnits.length) { + if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') { + break; + } + nalUnits.shift(); + } + + // return early if no video data has been observed + if (nalUnits.length === 0) { + this.trigger('done'); + return; + } + + while (nalUnits.length) { + currentNal = nalUnits.shift(); + + // record the track config + if (currentNal.nalUnitType === 'seq_parameter_set_rbsp') { + track.newMetadata = true; + config = currentNal.config; + track.width = config.width; + track.height = config.height; + track.sps = [currentNal.data]; + track.profileIdc = config.profileIdc; + track.levelIdc = config.levelIdc; + track.profileCompatibility = config.profileCompatibility; + h264Frame.endNalUnit(); + } else if (currentNal.nalUnitType === 'pic_parameter_set_rbsp') { + track.newMetadata = true; + track.pps = [currentNal.data]; + h264Frame.endNalUnit(); + } else if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') { + if (h264Frame) { + this.finishFrame(tags, h264Frame); + } + h264Frame = new FlvTag(FlvTag.VIDEO_TAG); + h264Frame.pts = currentNal.pts; + h264Frame.dts = currentNal.dts; + } else { + if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') { + // the current sample is a key frame + h264Frame.keyFrame = true; + } + h264Frame.endNalUnit(); + } + h264Frame.startNalUnit(); + h264Frame.writeBytes(currentNal.data); + } + if (h264Frame) { + this.finishFrame(tags, h264Frame); + } + + this.trigger('data', {track: track, tags: tags}); + + // Continue with the flush process now + this.trigger('done'); + }; +}; + +VideoSegmentStream.prototype = new Stream(); + +/** + * The final stage of the transmuxer that emits the flv tags + * for audio, video, and metadata. Also tranlates in time and + * outputs caption data and id3 cues. + */ +CoalesceStream = function(options) { + // Number of Tracks per output segment + // If greater than 1, we combine multiple + // tracks into a single segment + this.numberOfTracks = 0; + this.metadataStream = options.metadataStream; + + this.videoTags = []; + this.audioTags = []; + this.videoTrack = null; + this.audioTrack = null; + this.pendingCaptions = []; + this.pendingMetadata = []; + this.pendingTracks = 0; + + CoalesceStream.prototype.init.call(this); + + // Take output from multiple + this.push = function(output) { + // buffer incoming captions until the associated video segment + // finishes + if (output.text) { + return this.pendingCaptions.push(output); + } + // buffer incoming id3 tags until the final flush + if (output.frames) { + return this.pendingMetadata.push(output); + } + + if (output.track.type === 'video') { + this.videoTrack = output.track; + this.videoTags = output.tags; + this.pendingTracks++; + } + if (output.track.type === 'audio') { + this.audioTrack = output.track; + this.audioTags = output.tags; + this.pendingTracks++; + } + }; +}; + +CoalesceStream.prototype = new Stream(); +CoalesceStream.prototype.flush = function() { + var + id3, + caption, + i, + timelineStartPts, + event = { + tags: {}, + captions: [], + metadata: [] + }; + + if (this.pendingTracks < this.numberOfTracks) { + return; + } + + if (this.videoTrack) { + timelineStartPts = this.videoTrack.timelineStartInfo.pts; + } else if (this.audioTrack) { + timelineStartPts = this.audioTrack.timelineStartInfo.pts; + } + + event.tags.videoTags = this.videoTags; + event.tags.audioTags = this.audioTags; + + // Translate caption PTS times into second offsets into the + // video timeline for the segment + for (i = 0; i < this.pendingCaptions.length; i++) { + caption = this.pendingCaptions[i]; + caption.startTime = caption.startPts - timelineStartPts; + caption.startTime /= 90e3; + caption.endTime = caption.endPts - timelineStartPts; + caption.endTime /= 90e3; + event.captions.push(caption); + } + + // Translate ID3 frame PTS times into second offsets into the + // video timeline for the segment + for (i = 0; i < this.pendingMetadata.length; i++) { + id3 = this.pendingMetadata[i]; + id3.cueTime = id3.pts - timelineStartPts; + id3.cueTime /= 90e3; + event.metadata.push(id3); + } + // We add this to every single emitted segment even though we only need + // it for the first + event.metadata.dispatchType = this.metadataStream.dispatchType; + + // Reset stream state + this.videoTrack = null; + this.audioTrack = null; + this.videoTags = []; + this.audioTags = []; + this.pendingCaptions.length = 0; + this.pendingMetadata.length = 0; + this.pendingTracks = 0; + + // Emit the final segment + this.trigger('data', event); + + this.trigger('done'); +}; + +/** + * An object that incrementally transmuxes MPEG2 Trasport Stream + * chunks into an FLV. + */ +Transmuxer = function(options) { + var + self = this, + videoTrack, + audioTrack, + + packetStream, parseStream, elementaryStream, + adtsStream, h264Stream, + videoSegmentStream, audioSegmentStream, captionStream, + coalesceStream; + + Transmuxer.prototype.init.call(this); + + options = options || {}; + + // expose the metadata stream + this.metadataStream = new m2ts.MetadataStream(); + + options.metadataStream = this.metadataStream; + + // set up the parsing pipeline + packetStream = new m2ts.TransportPacketStream(); + parseStream = new m2ts.TransportParseStream(); + elementaryStream = new m2ts.ElementaryStream(); + adtsStream = new AdtsStream(); + h264Stream = new H264Stream(); + coalesceStream = new CoalesceStream(options); + + // disassemble MPEG2-TS packets into elementary streams + packetStream + .pipe(parseStream) + .pipe(elementaryStream); + + // !!THIS ORDER IS IMPORTANT!! + // demux the streams + elementaryStream + .pipe(h264Stream); + elementaryStream + .pipe(adtsStream); + + elementaryStream + .pipe(this.metadataStream) + .pipe(coalesceStream); + // if CEA-708 parsing is available, hook up a caption stream + captionStream = new m2ts.CaptionStream(); + h264Stream.pipe(captionStream) + .pipe(coalesceStream); + + // hook up the segment streams once track metadata is delivered + elementaryStream.on('data', function(data) { + var i, videoTrack, audioTrack; + + if (data.type === 'metadata') { + i = data.tracks.length; + + // scan the tracks listed in the metadata + while (i--) { + if (data.tracks[i].type === 'video') { + videoTrack = data.tracks[i]; + } else if (data.tracks[i].type === 'audio') { + audioTrack = data.tracks[i]; + } + } + + // hook up the video segment stream to the first track with h264 data + if (videoTrack && !videoSegmentStream) { + coalesceStream.numberOfTracks++; + videoSegmentStream = new VideoSegmentStream(videoTrack); + + // Set up the final part of the video pipeline + h264Stream + .pipe(videoSegmentStream) + .pipe(coalesceStream); + } + + if (audioTrack && !audioSegmentStream) { + // hook up the audio segment stream to the first track with aac data + coalesceStream.numberOfTracks++; + audioSegmentStream = new AudioSegmentStream(audioTrack); + + // Set up the final part of the audio pipeline + adtsStream + .pipe(audioSegmentStream) + .pipe(coalesceStream); + } + } + }); + + // feed incoming data to the front of the parsing pipeline + this.push = function(data) { + packetStream.push(data); + }; + + // flush any buffered data + this.flush = function() { + // Start at the top of the pipeline and flush all pending work + packetStream.flush(); + }; + + // Re-emit any data coming from the coalesce stream to the outside world + coalesceStream.on('data', function (event) { + self.trigger('data', event); + }); + + // Let the consumer know we have finished flushing the entire pipeline + coalesceStream.on('done', function () { + self.trigger('done'); + }); + + // For information on the FLV format, see + // http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf. + // Technically, this function returns the header and a metadata FLV tag + // if duration is greater than zero + // duration in seconds + // @return {object} the bytes of the FLV header as a Uint8Array + this.getFlvHeader = function(duration, audio, video) { // :ByteArray { + var + headBytes = new Uint8Array(3 + 1 + 1 + 4), + head = new DataView(headBytes.buffer), + metadata, + result, + metadataLength; + + // default arguments + duration = duration || 0; + audio = audio === undefined? true : audio; + video = video === undefined? true : video; + + // signature + head.setUint8(0, 0x46); // 'F' + head.setUint8(1, 0x4c); // 'L' + head.setUint8(2, 0x56); // 'V' + + // version + head.setUint8(3, 0x01); + + // flags + head.setUint8(4, (audio ? 0x04 : 0x00) | (video ? 0x01 : 0x00)); + + // data offset, should be 9 for FLV v1 + head.setUint32(5, headBytes.byteLength); + + // init the first FLV tag + if (duration <= 0) { + // no duration available so just write the first field of the first + // FLV tag + result = new Uint8Array(headBytes.byteLength + 4); + result.set(headBytes); + result.set([0, 0, 0, 0], headBytes.byteLength); + return result; + } + + // write out the duration metadata tag + metadata = new FlvTag(FlvTag.METADATA_TAG); + metadata.pts = metadata.dts = 0; + metadata.writeMetaDataDouble("duration", duration); + metadataLength = metadata.finalize().length; + result = new Uint8Array(headBytes.byteLength + metadataLength); + result.set(headBytes); + result.set(head.byteLength, metadataLength); + + return result; + }; +}; +Transmuxer.prototype = new Stream(); + +// forward compatibility +module.exports = Transmuxer; + +},{"../codecs/adts.js":76,"../codecs/h264":77,"../m2ts/m2ts.js":82,"../utils/stream.js":89,"./flv-tag.js":78}],81:[function(require,module,exports){ +/** + * mux.js + * + * Copyright (c) 2015 Brightcove + * All rights reserved. + * + * Reads in-band caption information from a video elementary + * stream. Captions must follow the CEA-708 standard for injection + * into an MPEG-2 transport streams. + * @see https://en.wikipedia.org/wiki/CEA-708 + */ + +'use strict'; + +// ----------------- +// Link To Transport +// ----------------- + +// Supplemental enhancement information (SEI) NAL units have a +// payload type field to indicate how they are to be +// interpreted. CEAS-708 caption content is always transmitted with +// payload type 0x04. +var USER_DATA_REGISTERED_ITU_T_T35 = 4, + RBSP_TRAILING_BITS = 128, + Stream = require('../utils/stream'); + +/** + * Parse a supplemental enhancement information (SEI) NAL unit. + * Stops parsing once a message of type ITU T T35 has been found. + * + * @param bytes {Uint8Array} the bytes of a SEI NAL unit + * @return {object} the parsed SEI payload + * @see Rec. ITU-T H.264, 7.3.2.3.1 + */ +var parseSei = function(bytes) { + var + i = 0, + result = { + payloadType: -1, + payloadSize: 0, + }, + payloadType = 0, + payloadSize = 0; + + // go through the sei_rbsp parsing each each individual sei_message + while (i < bytes.byteLength) { + // stop once we have hit the end of the sei_rbsp + if (bytes[i] === RBSP_TRAILING_BITS) { + break; + } + + // Parse payload type + while (bytes[i] === 0xFF) { + payloadType += 255; + i++; + } + payloadType += bytes[i++]; + + // Parse payload size + while (bytes[i] === 0xFF) { + payloadSize += 255; + i++; + } + payloadSize += bytes[i++]; + + // this sei_message is a 608/708 caption so save it and break + // there can only ever be one caption message in a frame's sei + if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) { + result.payloadType = payloadType; + result.payloadSize = payloadSize; + result.payload = bytes.subarray(i, i + payloadSize); + break; + } + + // skip the payload and parse the next message + i += payloadSize; + payloadType = 0; + payloadSize = 0; + } + + return result; +}; + +// see ANSI/SCTE 128-1 (2013), section 8.1 +var parseUserData = function(sei) { + // itu_t_t35_contry_code must be 181 (United States) for + // captions + if (sei.payload[0] !== 181) { + return null; + } + + // itu_t_t35_provider_code should be 49 (ATSC) for captions + if (((sei.payload[1] << 8) | sei.payload[2]) !== 49) { + return null; + } + + // the user_identifier should be "GA94" to indicate ATSC1 data + if (String.fromCharCode(sei.payload[3], + sei.payload[4], + sei.payload[5], + sei.payload[6]) !== 'GA94') { + return null; + } + + // finally, user_data_type_code should be 0x03 for caption data + if (sei.payload[7] !== 0x03) { + return null; + } + + // return the user_data_type_structure and strip the trailing + // marker bits + return sei.payload.subarray(8, sei.payload.length - 1); +}; + +// see CEA-708-D, section 4.4 +var parseCaptionPackets = function(pts, userData) { + var results = [], i, count, offset, data; + + // if this is just filler, return immediately + if (!(userData[0] & 0x40)) { + return results; + } + + // parse out the cc_data_1 and cc_data_2 fields + count = userData[0] & 0x1f; + for (i = 0; i < count; i++) { + offset = i * 3; + data = { + type: userData[offset + 2] & 0x03, + pts: pts + }; + + // capture cc data when cc_valid is 1 + if (userData[offset + 2] & 0x04) { + data.ccData = (userData[offset + 3] << 8) | userData[offset + 4]; + results.push(data); + } + } + return results; +}; + +var CaptionStream = function() { + var self = this; + CaptionStream.prototype.init.call(this); + + this.captionPackets_ = []; + + this.field1_ = new Cea608Stream(); + + // forward data and done events from field1_ to this CaptionStream + this.field1_.on('data', this.trigger.bind(this, 'data')); + this.field1_.on('done', this.trigger.bind(this, 'done')); +}; +CaptionStream.prototype = new Stream(); +CaptionStream.prototype.push = function(event) { + var sei, userData, captionPackets; + + // only examine SEI NALs + if (event.nalUnitType !== 'sei_rbsp') { + return; + } + + // parse the sei + sei = parseSei(event.escapedRBSP); + + // ignore everything but user_data_registered_itu_t_t35 + if (sei.payloadType !== USER_DATA_REGISTERED_ITU_T_T35) { + return; + } + + // parse out the user data payload + userData = parseUserData(sei); + + // ignore unrecognized userData + if (!userData) { + return; + } + + // parse out CC data packets and save them for later + this.captionPackets_ = this.captionPackets_.concat(parseCaptionPackets(event.pts, userData)); +}; + +CaptionStream.prototype.flush = function () { + // make sure we actually parsed captions before proceeding + if (!this.captionPackets_.length) { + this.field1_.flush(); + return; + } + + // sort caption byte-pairs based on their PTS values + this.captionPackets_.sort(function(a, b) { + return a.pts - b.pts; + }); + + // Push each caption into Cea608Stream + this.captionPackets_.forEach(this.field1_.push, this.field1_); + + this.captionPackets_.length = 0; + this.field1_.flush(); + return; +}; +// ---------------------- +// Session to Application +// ---------------------- + +var BASIC_CHARACTER_TRANSLATION = { + 0x2a: 0xe1, + 0x5c: 0xe9, + 0x5e: 0xed, + 0x5f: 0xf3, + 0x60: 0xfa, + 0x7b: 0xe7, + 0x7c: 0xf7, + 0x7d: 0xd1, + 0x7e: 0xf1, + 0x7f: 0x2588 +}; + +var getCharFromCode = function(code) { + if(code === null) { + return ''; + } + code = BASIC_CHARACTER_TRANSLATION[code] || code; + return String.fromCharCode(code); +}; + +// Constants for the byte codes recognized by Cea608Stream. This +// list is not exhaustive. For a more comprehensive listing and +// semantics see +// http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf +var PADDING = 0x0000, + + // Pop-on Mode + RESUME_CAPTION_LOADING = 0x1420, + END_OF_CAPTION = 0x142f, + + // Roll-up Mode + ROLL_UP_2_ROWS = 0x1425, + ROLL_UP_3_ROWS = 0x1426, + ROLL_UP_4_ROWS = 0x1427, + RESUME_DIRECT_CAPTIONING = 0x1429, + CARRIAGE_RETURN = 0x142d, + // Erasure + BACKSPACE = 0x1421, + ERASE_DISPLAYED_MEMORY = 0x142c, + ERASE_NON_DISPLAYED_MEMORY = 0x142e; + +// the index of the last row in a CEA-608 display buffer +var BOTTOM_ROW = 14; +// CEA-608 captions are rendered onto a 34x15 matrix of character +// cells. The "bottom" row is the last element in the outer array. +var createDisplayBuffer = function() { + var result = [], i = BOTTOM_ROW + 1; + while (i--) { + result.push(''); + } + return result; +}; + +var Cea608Stream = function() { + Cea608Stream.prototype.init.call(this); + + this.mode_ = 'popOn'; + // When in roll-up mode, the index of the last row that will + // actually display captions. If a caption is shifted to a row + // with a lower index than this, it is cleared from the display + // buffer + this.topRow_ = 0; + this.startPts_ = 0; + this.displayed_ = createDisplayBuffer(); + this.nonDisplayed_ = createDisplayBuffer(); + this.lastControlCode_ = null; + + this.push = function(packet) { + // Ignore other channels + if (packet.type !== 0) { + return; + } + var data, swap, char0, char1; + // remove the parity bits + data = packet.ccData & 0x7f7f; + + // ignore duplicate control codes + if (data === this.lastControlCode_) { + this.lastControlCode_ = null; + return; + } + + // Store control codes + if ((data & 0xf000) === 0x1000) { + this.lastControlCode_ = data; + } else { + this.lastControlCode_ = null; + } + + switch (data) { + case PADDING: + break; + case RESUME_CAPTION_LOADING: + this.mode_ = 'popOn'; + break; + case END_OF_CAPTION: + // if a caption was being displayed, it's gone now + this.flushDisplayed(packet.pts); + + // flip memory + swap = this.displayed_; + this.displayed_ = this.nonDisplayed_; + this.nonDisplayed_ = swap; + + // start measuring the time to display the caption + this.startPts_ = packet.pts; + break; + + case ROLL_UP_2_ROWS: + this.topRow_ = BOTTOM_ROW - 1; + this.mode_ = 'rollUp'; + break; + case ROLL_UP_3_ROWS: + this.topRow_ = BOTTOM_ROW - 2; + this.mode_ = 'rollUp'; + break; + case ROLL_UP_4_ROWS: + this.topRow_ = BOTTOM_ROW - 3; + this.mode_ = 'rollUp'; + break; + case CARRIAGE_RETURN: + this.flushDisplayed(packet.pts); + this.shiftRowsUp_(); + this.startPts_ = packet.pts; + break; + + case BACKSPACE: + if (this.mode_ === 'popOn') { + this.nonDisplayed_[BOTTOM_ROW] = this.nonDisplayed_[BOTTOM_ROW].slice(0, -1); + } else { + this.displayed_[BOTTOM_ROW] = this.displayed_[BOTTOM_ROW].slice(0, -1); + } + break; + case ERASE_DISPLAYED_MEMORY: + this.flushDisplayed(packet.pts); + this.displayed_ = createDisplayBuffer(); + break; + case ERASE_NON_DISPLAYED_MEMORY: + this.nonDisplayed_ = createDisplayBuffer(); + break; + default: + char0 = data >>> 8; + char1 = data & 0xff; + + // Look for a Channel 1 Preamble Address Code + if (char0 >= 0x10 && char0 <= 0x17 && + char1 >= 0x40 && char1 <= 0x7F && + (char0 !== 0x10 || char1 < 0x60)) { + // Follow Safari's lead and replace the PAC with a space + char0 = 0x20; + // we only want one space so make the second character null + // which will get become '' in getCharFromCode + char1 = null; + } + + // Look for special character sets + if ((char0 === 0x11 || char0 === 0x19) && + (char1 >= 0x30 && char1 <= 0x3F)) { + // Put in eigth note and space + char0 = 0x266A; + char1 = ''; + } + + // ignore unsupported control codes + if ((char0 & 0xf0) === 0x10) { + return; + } + + // character handling is dependent on the current mode + this[this.mode_](packet.pts, char0, char1); + break; + } + }; +}; +Cea608Stream.prototype = new Stream(); +// Trigger a cue point that captures the current state of the +// display buffer +Cea608Stream.prototype.flushDisplayed = function(pts) { + var content = this.displayed_ + // remove spaces from the start and end of the string + .map(function(row) { return row.trim(); }) + // remove empty rows + .filter(function(row) { return row.length; }) + // combine all text rows to display in one cue + .join('\n'); + + if (content.length) { + this.trigger('data', { + startPts: this.startPts_, + endPts: pts, + text: content + }); + } +}; + +// Mode Implementations +Cea608Stream.prototype.popOn = function(pts, char0, char1) { + var baseRow = this.nonDisplayed_[BOTTOM_ROW]; + + // buffer characters + baseRow += getCharFromCode(char0); + baseRow += getCharFromCode(char1); + this.nonDisplayed_[BOTTOM_ROW] = baseRow; +}; + +Cea608Stream.prototype.rollUp = function(pts, char0, char1) { + var baseRow = this.displayed_[BOTTOM_ROW]; + if (baseRow === '') { + // we're starting to buffer new display input, so flush out the + // current display + this.flushDisplayed(pts); + + this.startPts_ = pts; + } + + baseRow += getCharFromCode(char0); + baseRow += getCharFromCode(char1); + + this.displayed_[BOTTOM_ROW] = baseRow; +}; +Cea608Stream.prototype.shiftRowsUp_ = function() { + var i; + // clear out inactive rows + for (i = 0; i < this.topRow_; i++) { + this.displayed_[i] = ''; + } + // shift displayed rows up + for (i = this.topRow_; i < BOTTOM_ROW; i++) { + this.displayed_[i] = this.displayed_[i + 1]; + } + // clear out the bottom row + this.displayed_[BOTTOM_ROW] = ''; +}; + +// exports +module.exports = { + CaptionStream: CaptionStream, + Cea608Stream: Cea608Stream, +}; + + +},{"../utils/stream":89}],82:[function(require,module,exports){ +/** + * mux.js + * + * Copyright (c) 2015 Brightcove + * All rights reserved. + * + * A stream-based mp2t to mp4 converter. This utility can be used to + * deliver mp4s to a SourceBuffer on platforms that support native + * Media Source Extensions. + */ +'use strict'; +var Stream = require('../utils/stream.js'), + CaptionStream = require('./caption-stream'), + StreamTypes = require('./stream-types'); + +var Stream = require('../utils/stream.js'); +var m2tsStreamTypes = require('./stream-types.js'); + +// object types +var + TransportPacketStream, TransportParseStream, ElementaryStream, + AacStream, H264Stream, NalByteStream; + +// constants +var + MP2T_PACKET_LENGTH = 188, // bytes + SYNC_BYTE = 0x47, + +/** + * Splits an incoming stream of binary data into MPEG-2 Transport + * Stream packets. + */ +TransportPacketStream = function() { + var + buffer = new Uint8Array(MP2T_PACKET_LENGTH), + bytesInBuffer = 0; + + TransportPacketStream.prototype.init.call(this); + + // Deliver new bytes to the stream. + + this.push = function(bytes) { + var + i = 0, + startIndex = 0, + endIndex = MP2T_PACKET_LENGTH, + everything; + + // If there are bytes remaining from the last segment, prepend them to the + // bytes that were pushed in + if (bytesInBuffer) { + everything = new Uint8Array(bytes.byteLength + bytesInBuffer); + everything.set(buffer.subarray(0, bytesInBuffer)); + everything.set(bytes, bytesInBuffer); + bytesInBuffer = 0; + } else { + everything = bytes; + } + + // While we have enough data for a packet + while (endIndex < everything.byteLength) { + // Look for a pair of start and end sync bytes in the data.. + if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) { + // We found a packet so emit it and jump one whole packet forward in + // the stream + this.trigger('data', everything.subarray(startIndex, endIndex)); + startIndex += MP2T_PACKET_LENGTH; + endIndex += MP2T_PACKET_LENGTH; + continue; + } + // If we get here, we have somehow become de-synchronized and we need to step + // forward one byte at a time until we find a pair of sync bytes that denote + // a packet + startIndex++; + endIndex++; + } + + // If there was some data left over at the end of the segment that couldn't + // possibly be a whole packet, keep it because it might be the start of a packet + // that continues in the next segment + if (startIndex < everything.byteLength) { + buffer.set(everything.subarray(startIndex), 0); + bytesInBuffer = everything.byteLength - startIndex; + } + }; + + this.flush = function () { + // If the buffer contains a whole packet when we are being flushed, emit it + // and empty the buffer. Otherwise hold onto the data because it may be + // important for decoding the next segment + if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) { + this.trigger('data', buffer); + bytesInBuffer = 0; + } + this.trigger('done'); + }; +}; +TransportPacketStream.prototype = new Stream(); + +/** + * Accepts an MP2T TransportPacketStream and emits data events with parsed + * forms of the individual transport stream packets. + */ +TransportParseStream = function() { + var parsePsi, parsePat, parsePmt, parsePes, self; + TransportParseStream.prototype.init.call(this); + self = this; + + this.packetsWaitingForPmt = []; + this.programMapTable = undefined; + + parsePsi = function(payload, psi) { + var offset = 0; + + // PSI packets may be split into multiple sections and those + // sections may be split into multiple packets. If a PSI + // section starts in this packet, the payload_unit_start_indicator + // will be true and the first byte of the payload will indicate + // the offset from the current position to the start of the + // section. + if (psi.payloadUnitStartIndicator) { + offset += payload[offset] + 1; + } + + if (psi.type === 'pat') { + parsePat(payload.subarray(offset), psi); + } else { + parsePmt(payload.subarray(offset), psi); + } + }; + + parsePat = function(payload, pat) { + pat.section_number = payload[7]; + pat.last_section_number = payload[8]; + + // skip the PSI header and parse the first PMT entry + self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11]; + pat.pmtPid = self.pmtPid; + }; + + /** + * Parse out the relevant fields of a Program Map Table (PMT). + * @param payload {Uint8Array} the PMT-specific portion of an MP2T + * packet. The first byte in this array should be the table_id + * field. + * @param pmt {object} the object that should be decorated with + * fields parsed from the PMT. + */ + parsePmt = function(payload, pmt) { + var sectionLength, tableEnd, programInfoLength, offset; + + // PMTs can be sent ahead of the time when they should actually + // take effect. We don't believe this should ever be the case + // for HLS but we'll ignore "forward" PMT declarations if we see + // them. Future PMT declarations have the current_next_indicator + // set to zero. + if (!(payload[5] & 0x01)) { + return; + } + + // overwrite any existing program map table + self.programMapTable = {}; + + // the mapping table ends at the end of the current section + sectionLength = (payload[1] & 0x0f) << 8 | payload[2]; + tableEnd = 3 + sectionLength - 4; + + // to determine where the table is, we have to figure out how + // long the program info descriptors are + programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; + + // advance the offset to the first entry in the mapping table + offset = 12 + programInfoLength; + while (offset < tableEnd) { + // add an entry that maps the elementary_pid to the stream_type + self.programMapTable[(payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]] = payload[offset]; + + // move to the next table entry + // skip past the elementary stream descriptors, if present + offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5; + } + + // record the map on the packet as well + pmt.programMapTable = self.programMapTable; + + // if there are any packets waiting for a PMT to be found, process them now + while (self.packetsWaitingForPmt.length) { + self.processPes_.apply(self, self.packetsWaitingForPmt.shift()); + } + }; + + /** + * Deliver a new MP2T packet to the stream. + */ + this.push = function(packet) { + var + result = {}, + offset = 4; + + result.payloadUnitStartIndicator = !!(packet[1] & 0x40); + + // pid is a 13-bit field starting at the last bit of packet[1] + result.pid = packet[1] & 0x1f; + result.pid <<= 8; + result.pid |= packet[2]; + + // if an adaption field is present, its length is specified by the + // fifth byte of the TS packet header. The adaptation field is + // used to add stuffing to PES packets that don't fill a complete + // TS packet, and to specify some forms of timing and control data + // that we do not currently use. + if (((packet[3] & 0x30) >>> 4) > 0x01) { + offset += packet[offset] + 1; + } + + // parse the rest of the packet based on the type + if (result.pid === 0) { + result.type = 'pat'; + parsePsi(packet.subarray(offset), result); + this.trigger('data', result); + } else if (result.pid === this.pmtPid) { + result.type = 'pmt'; + parsePsi(packet.subarray(offset), result); + this.trigger('data', result); + } else if (this.programMapTable === undefined) { + // When we have not seen a PMT yet, defer further processing of + // PES packets until one has been parsed + this.packetsWaitingForPmt.push([packet, offset, result]); + } else { + this.processPes_(packet, offset, result); + } + }; + + this.processPes_ = function (packet, offset, result) { + result.streamType = this.programMapTable[result.pid]; + result.type = 'pes'; + result.data = packet.subarray(offset); + + this.trigger('data', result); + }; + +}; +TransportParseStream.prototype = new Stream(); +TransportParseStream.STREAM_TYPES = { + h264: 0x1b, + adts: 0x0f +}; + +/** + * Reconsistutes program elementary stream (PES) packets from parsed + * transport stream packets. That is, if you pipe an + * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output + * events will be events which capture the bytes for individual PES + * packets plus relevant metadata that has been extracted from the + * container. + */ +ElementaryStream = function() { + var + // PES packet fragments + video = { + data: [], + size: 0 + }, + audio = { + data: [], + size: 0 + }, + timedMetadata = { + data: [], + size: 0 + }, + parsePes = function(payload, pes) { + var ptsDtsFlags; + + // find out if this packets starts a new keyframe + pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; + // PES packets may be annotated with a PTS value, or a PTS value + // and a DTS value. Determine what combination of values is + // available to work with. + ptsDtsFlags = payload[7]; + + // PTS and DTS are normally stored as a 33-bit number. Javascript + // performs all bitwise operations on 32-bit integers but javascript + // supports a much greater range (52-bits) of integer using standard + // mathematical operations. + // We construct a 31-bit value using bitwise operators over the 31 + // most significant bits and then multiply by 4 (equal to a left-shift + // of 2) before we add the final 2 least significant bits of the + // timestamp (equal to an OR.) + if (ptsDtsFlags & 0xC0) { + // the PTS and DTS are not written out directly. For information + // on how they are encoded, see + // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html + pes.pts = (payload[9] & 0x0E) << 27 + | (payload[10] & 0xFF) << 20 + | (payload[11] & 0xFE) << 12 + | (payload[12] & 0xFF) << 5 + | (payload[13] & 0xFE) >>> 3; + pes.pts *= 4; // Left shift by 2 + pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs + pes.dts = pes.pts; + if (ptsDtsFlags & 0x40) { + pes.dts = (payload[14] & 0x0E ) << 27 + | (payload[15] & 0xFF ) << 20 + | (payload[16] & 0xFE ) << 12 + | (payload[17] & 0xFF ) << 5 + | (payload[18] & 0xFE ) >>> 3; + pes.dts *= 4; // Left shift by 2 + pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs + } + } + + // the data section starts immediately after the PES header. + // pes_header_data_length specifies the number of header bytes + // that follow the last byte of the field. + pes.data = payload.subarray(9 + payload[8]); + }, + flushStream = function(stream, type) { + var + packetData = new Uint8Array(stream.size), + event = { + type: type + }, + i = 0, + fragment; + + // do nothing if there is no buffered data + if (!stream.data.length) { + return; + } + event.trackId = stream.data[0].pid; + + // reassemble the packet + while (stream.data.length) { + fragment = stream.data.shift(); + + packetData.set(fragment.data, i); + i += fragment.data.byteLength; + } + + // parse assembled packet's PES header + parsePes(packetData, event); + + stream.size = 0; + + self.trigger('data', event); + }, + self; + + ElementaryStream.prototype.init.call(this); + self = this; + + this.push = function(data) { + ({ + pat: function() { + // we have to wait for the PMT to arrive as well before we + // have any meaningful metadata + }, + pes: function() { + var stream, streamType; + + switch (data.streamType) { + case StreamTypes.H264_STREAM_TYPE: + case m2tsStreamTypes.H264_STREAM_TYPE: + stream = video; + streamType = 'video'; + break; + case StreamTypes.ADTS_STREAM_TYPE: + stream = audio; + streamType = 'audio'; + break; + case StreamTypes.METADATA_STREAM_TYPE: + stream = timedMetadata; + streamType = 'timed-metadata'; + break; + default: + // ignore unknown stream types + return; + } + + // if a new packet is starting, we can flush the completed + // packet + if (data.payloadUnitStartIndicator) { + flushStream(stream, streamType); + } + + // buffer this fragment until we are sure we've received the + // complete payload + stream.data.push(data); + stream.size += data.data.byteLength; + }, + pmt: function() { + var + event = { + type: 'metadata', + tracks: [] + }, + programMapTable = data.programMapTable, + k, + track; + + // translate streams to tracks + for (k in programMapTable) { + if (programMapTable.hasOwnProperty(k)) { + track = { + timelineStartInfo: { + baseMediaDecodeTime: 0 + } + }; + track.id = +k; + if (programMapTable[k] === m2tsStreamTypes.H264_STREAM_TYPE) { + track.codec = 'avc'; + track.type = 'video'; + } else if (programMapTable[k] === m2tsStreamTypes.ADTS_STREAM_TYPE) { + track.codec = 'adts'; + track.type = 'audio'; + } + event.tracks.push(track); + } + } + self.trigger('data', event); + } + })[data.type](); + }; + + /** + * Flush any remaining input. Video PES packets may be of variable + * length. Normally, the start of a new video packet can trigger the + * finalization of the previous packet. That is not possible if no + * more video is forthcoming, however. In that case, some other + * mechanism (like the end of the file) has to be employed. When it is + * clear that no additional data is forthcoming, calling this method + * will flush the buffered packets. + */ + this.flush = function() { + // !!THIS ORDER IS IMPORTANT!! + // video first then audio + flushStream(video, 'video'); + flushStream(audio, 'audio'); + flushStream(timedMetadata, 'timed-metadata'); + this.trigger('done'); + }; +}; +ElementaryStream.prototype = new Stream(); + +var m2ts = { + PAT_PID: 0x0000, + MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH, + TransportPacketStream: TransportPacketStream, + TransportParseStream: TransportParseStream, + ElementaryStream: ElementaryStream, + CaptionStream: CaptionStream.CaptionStream, + Cea608Stream: CaptionStream.Cea608Stream, + MetadataStream: require('./metadata-stream'), +}; + +for (var type in StreamTypes) { + if (StreamTypes.hasOwnProperty(type)) { + m2ts[type] = StreamTypes[type]; + } +} + +module.exports = m2ts; + +},{"../utils/stream.js":89,"./caption-stream":81,"./metadata-stream":83,"./stream-types":84,"./stream-types.js":84}],83:[function(require,module,exports){ +/** + * Accepts program elementary stream (PES) data events and parses out + * ID3 metadata from them, if present. + * @see http://id3.org/id3v2.3.0 + */ +'use strict'; +var + Stream = require('../utils/stream'), + StreamTypes = require('./stream-types'), + // return a percent-encoded representation of the specified byte range + // @see http://en.wikipedia.org/wiki/Percent-encoding + percentEncode = function(bytes, start, end) { + var i, result = ''; + for (i = start; i < end; i++) { + result += '%' + ('00' + bytes[i].toString(16)).slice(-2); + } + return result; + }, + // return the string representation of the specified byte range, + // interpreted as UTf-8. + parseUtf8 = function(bytes, start, end) { + return decodeURIComponent(percentEncode(bytes, start, end)); + }, + // return the string representation of the specified byte range, + // interpreted as ISO-8859-1. + parseIso88591 = function(bytes, start, end) { + return unescape(percentEncode(bytes, start, end)); // jshint ignore:line + }, + parseSyncSafeInteger = function (data) { + return (data[0] << 21) | + (data[1] << 14) | + (data[2] << 7) | + (data[3]); + }, + tagParsers = { + 'TXXX': function(tag) { + var i; + if (tag.data[0] !== 3) { + // ignore frames with unrecognized character encodings + return; + } + + for (i = 1; i < tag.data.length; i++) { + if (tag.data[i] === 0) { + // parse the text fields + tag.description = parseUtf8(tag.data, 1, i); + // do not include the null terminator in the tag value + tag.value = parseUtf8(tag.data, i + 1, tag.data.length - 1); + break; + } + } + tag.data = tag.value; + }, + 'WXXX': function(tag) { + var i; + if (tag.data[0] !== 3) { + // ignore frames with unrecognized character encodings + return; + } + + for (i = 1; i < tag.data.length; i++) { + if (tag.data[i] === 0) { + // parse the description and URL fields + tag.description = parseUtf8(tag.data, 1, i); + tag.url = parseUtf8(tag.data, i + 1, tag.data.length); + break; + } + } + }, + 'PRIV': function(tag) { + var i; + + for (i = 0; i < tag.data.length; i++) { + if (tag.data[i] === 0) { + // parse the description and URL fields + tag.owner = parseIso88591(tag.data, 0, i); + break; + } + } + tag.privateData = tag.data.subarray(i + 1); + tag.data = tag.privateData; + } + }, + MetadataStream; + +MetadataStream = function(options) { + var + settings = { + debug: !!(options && options.debug), + + // the bytes of the program-level descriptor field in MP2T + // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and + // program element descriptors" + descriptor: options && options.descriptor + }, + // the total size in bytes of the ID3 tag being parsed + tagSize = 0, + // tag data that is not complete enough to be parsed + buffer = [], + // the total number of bytes currently in the buffer + bufferSize = 0, + i; + + MetadataStream.prototype.init.call(this); + + // calculate the text track in-band metadata track dispatch type + // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track + this.dispatchType = StreamTypes.METADATA_STREAM_TYPE.toString(16); + if (settings.descriptor) { + for (i = 0; i < settings.descriptor.length; i++) { + this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2); + } + } + + this.push = function(chunk) { + var tag, frameStart, frameSize, frame, i, frameHeader; + if (chunk.type !== 'timed-metadata') { + return; + } + + // if data_alignment_indicator is set in the PES header, + // we must have the start of a new ID3 tag. Assume anything + // remaining in the buffer was malformed and throw it out + if (chunk.dataAlignmentIndicator) { + bufferSize = 0; + buffer.length = 0; + } + + // ignore events that don't look like ID3 data + if (buffer.length === 0 && + (chunk.data.length < 10 || + chunk.data[0] !== 'I'.charCodeAt(0) || + chunk.data[1] !== 'D'.charCodeAt(0) || + chunk.data[2] !== '3'.charCodeAt(0))) { + if (settings.debug) { + console.log('Skipping unrecognized metadata packet'); + } + return; + } + + // add this chunk to the data we've collected so far + + buffer.push(chunk); + bufferSize += chunk.data.byteLength; + + // grab the size of the entire frame from the ID3 header + if (buffer.length === 1) { + // the frame size is transmitted as a 28-bit integer in the + // last four bytes of the ID3 header. + // The most significant bit of each byte is dropped and the + // results concatenated to recover the actual value. + tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10)); + + // ID3 reports the tag size excluding the header but it's more + // convenient for our comparisons to include it + tagSize += 10; + } + + // if the entire frame has not arrived, wait for more data + if (bufferSize < tagSize) { + return; + } + + // collect the entire frame so it can be parsed + tag = { + data: new Uint8Array(tagSize), + frames: [], + pts: buffer[0].pts, + dts: buffer[0].dts + }; + for (i = 0; i < tagSize;) { + tag.data.set(buffer[0].data.subarray(0, tagSize - i), i); + i += buffer[0].data.byteLength; + bufferSize -= buffer[0].data.byteLength; + buffer.shift(); + } + + // find the start of the first frame and the end of the tag + frameStart = 10; + if (tag.data[5] & 0x40) { + // advance the frame start past the extended header + frameStart += 4; // header size field + frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14)); + + // clip any padding off the end + tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20)); + } + + // parse one or more ID3 frames + // http://id3.org/id3v2.3.0#ID3v2_frame_overview + do { + // determine the number of bytes in this frame + frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8)); + if (frameSize < 1) { + return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.'); + } + frameHeader = String.fromCharCode(tag.data[frameStart], + tag.data[frameStart + 1], + tag.data[frameStart + 2], + tag.data[frameStart + 3]); + + + frame = { + id: frameHeader, + data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10) + }; + frame.key = frame.id; + if (tagParsers[frame.id]) { + tagParsers[frame.id](frame); + if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') { + var + d = frame.data, + size = ((d[3] & 0x01) << 30) | + (d[4] << 22) | + (d[5] << 14) | + (d[6] << 6) | + (d[7] >>> 2); + + size *= 4; + size += d[7] & 0x03; + frame.timeStamp = size; + this.trigger('timestamp', frame); + } + } + tag.frames.push(frame); + + frameStart += 10; // advance past the frame header + frameStart += frameSize; // advance past the frame body + } while (frameStart < tagSize); + this.trigger('data', tag); + }; +}; +MetadataStream.prototype = new Stream(); + +module.exports = MetadataStream; + +},{"../utils/stream":89,"./stream-types":84}],84:[function(require,module,exports){ +'use strict'; + +module.exports = { + H264_STREAM_TYPE: 0x1B, + ADTS_STREAM_TYPE: 0x0F, + METADATA_STREAM_TYPE: 0x15 +}; + +},{}],85:[function(require,module,exports){ +module.exports = { + generator: require('./mp4-generator'), + Transmuxer: require('./transmuxer').Transmuxer, + AudioSegmentStream: require('./transmuxer').AudioSegmentStream, + VideoSegmentStream: require('./transmuxer').VideoSegmentStream +}; + +},{"./mp4-generator":86,"./transmuxer":87}],86:[function(require,module,exports){ +/** + * mux.js + * + * Copyright (c) 2015 Brightcove + * All rights reserved. + * + * Functions that generate fragmented MP4s suitable for use with Media + * Source Extensions. + */ +'use strict'; + +var box, dinf, esds, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd, trak, + tkhd, mdia, mdhd, hdlr, sdtp, stbl, stsd, styp, traf, trex, trun, + types, MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR, + AUDIO_HDLR, HDLR_TYPES, VMHD, SMHD, DREF, STCO, STSC, STSZ, STTS; + +// pre-calculate constants +(function() { + var i; + types = { + avc1: [], // codingname + avcC: [], + btrt: [], + dinf: [], + dref: [], + esds: [], + ftyp: [], + hdlr: [], + mdat: [], + mdhd: [], + mdia: [], + mfhd: [], + minf: [], + moof: [], + moov: [], + mp4a: [], // codingname + mvex: [], + mvhd: [], + sdtp: [], + smhd: [], + stbl: [], + stco: [], + stsc: [], + stsd: [], + stsz: [], + stts: [], + styp: [], + tfdt: [], + tfhd: [], + traf: [], + trak: [], + trun: [], + trex: [], + tkhd: [], + vmhd: [] + }; + + // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we + // don't throw an error + if (typeof Uint8Array === 'undefined') { + return; + } + + for (i in types) { + if (types.hasOwnProperty(i)) { + types[i] = [ + i.charCodeAt(0), + i.charCodeAt(1), + i.charCodeAt(2), + i.charCodeAt(3) + ]; + } + } + + MAJOR_BRAND = new Uint8Array([ + 'i'.charCodeAt(0), + 's'.charCodeAt(0), + 'o'.charCodeAt(0), + 'm'.charCodeAt(0) + ]); + AVC1_BRAND = new Uint8Array([ + 'a'.charCodeAt(0), + 'v'.charCodeAt(0), + 'c'.charCodeAt(0), + '1'.charCodeAt(0) + ]); + MINOR_VERSION = new Uint8Array([0, 0, 0, 1]); + VIDEO_HDLR = new Uint8Array([ + 0x00, // version 0 + 0x00, 0x00, 0x00, // flags + 0x00, 0x00, 0x00, 0x00, // pre_defined + 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide' + 0x00, 0x00, 0x00, 0x00, // reserved + 0x00, 0x00, 0x00, 0x00, // reserved + 0x00, 0x00, 0x00, 0x00, // reserved + 0x56, 0x69, 0x64, 0x65, + 0x6f, 0x48, 0x61, 0x6e, + 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler' + ]); + AUDIO_HDLR = new Uint8Array([ + 0x00, // version 0 + 0x00, 0x00, 0x00, // flags + 0x00, 0x00, 0x00, 0x00, // pre_defined + 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun' + 0x00, 0x00, 0x00, 0x00, // reserved + 0x00, 0x00, 0x00, 0x00, // reserved + 0x00, 0x00, 0x00, 0x00, // reserved + 0x53, 0x6f, 0x75, 0x6e, + 0x64, 0x48, 0x61, 0x6e, + 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler' + ]); + HDLR_TYPES = { + "video":VIDEO_HDLR, + "audio": AUDIO_HDLR + }; + DREF = new Uint8Array([ + 0x00, // version 0 + 0x00, 0x00, 0x00, // flags + 0x00, 0x00, 0x00, 0x01, // entry_count + 0x00, 0x00, 0x00, 0x0c, // entry_size + 0x75, 0x72, 0x6c, 0x20, // 'url' type + 0x00, // version 0 + 0x00, 0x00, 0x01 // entry_flags + ]); + SMHD = new Uint8Array([ + 0x00, // version + 0x00, 0x00, 0x00, // flags + 0x00, 0x00, // balance, 0 means centered + 0x00, 0x00 // reserved + ]); + STCO = new Uint8Array([ + 0x00, // version + 0x00, 0x00, 0x00, // flags + 0x00, 0x00, 0x00, 0x00 // entry_count + ]); + STSC = STCO; + STSZ = new Uint8Array([ + 0x00, // version + 0x00, 0x00, 0x00, // flags + 0x00, 0x00, 0x00, 0x00, // sample_size + 0x00, 0x00, 0x00, 0x00, // sample_count + ]); + STTS = STCO; + VMHD = new Uint8Array([ + 0x00, // version + 0x00, 0x00, 0x01, // flags + 0x00, 0x00, // graphicsmode + 0x00, 0x00, + 0x00, 0x00, + 0x00, 0x00 // opcolor + ]); +})(); + +box = function(type) { + var + payload = [], + size = 0, + i, + result, + view; + + for (i = 1; i < arguments.length; i++) { + payload.push(arguments[i]); + } + + i = payload.length; + + // calculate the total size we need to allocate + while (i--) { + size += payload[i].byteLength; + } + result = new Uint8Array(size + 8); + view = new DataView(result.buffer, result.byteOffset, result.byteLength); + view.setUint32(0, result.byteLength); + result.set(type, 4); + + // copy the payload into the result + for (i = 0, size = 8; i < payload.length; i++) { + result.set(payload[i], size); + size += payload[i].byteLength; + } + return result; +}; + +dinf = function() { + return box(types.dinf, box(types.dref, DREF)); +}; + +esds = function(track) { + return box(types.esds, new Uint8Array([ + 0x00, // version + 0x00, 0x00, 0x00, // flags + + // ES_Descriptor + 0x03, // tag, ES_DescrTag + 0x19, // length + 0x00, 0x00, // ES_ID + 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority + + // DecoderConfigDescriptor + 0x04, // tag, DecoderConfigDescrTag + 0x11, // length + 0x40, // object type + 0x15, // streamType + 0x00, 0x06, 0x00, // bufferSizeDB + 0x00, 0x00, 0xda, 0xc0, // maxBitrate + 0x00, 0x00, 0xda, 0xc0, // avgBitrate + + // DecoderSpecificInfo + 0x05, // tag, DecoderSpecificInfoTag + 0x02, // length + // ISO/IEC 14496-3, AudioSpecificConfig + // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35 + (track.audioobjecttype << 3) | (track.samplingfrequencyindex >>> 1), + (track.samplingfrequencyindex << 7) | (track.channelcount << 3), + 0x06, 0x01, 0x02 // GASpecificConfig + ])); +}; + +ftyp = function() { + return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND); +}; + +hdlr = function(type) { + return box(types.hdlr, HDLR_TYPES[type]); +}; +mdat = function(data) { + return box(types.mdat, data); +}; +mdhd = function(track) { + var result = new Uint8Array([ + 0x00, // version 0 + 0x00, 0x00, 0x00, // flags + 0x00, 0x00, 0x00, 0x02, // creation_time + 0x00, 0x00, 0x00, 0x03, // modification_time + 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second + + (track.duration >>> 24) & 0xFF, + (track.duration >>> 16) & 0xFF, + (track.duration >>> 8) & 0xFF, + track.duration & 0xFF, // duration + 0x55, 0xc4, // 'und' language (undetermined) + 0x00, 0x00 + ]); + + // Use the sample rate from the track metadata, when it is + // defined. The sample rate can be parsed out of an ADTS header, for + // instance. + if (track.samplerate) { + result[12] = (track.samplerate >>> 24) & 0xFF; + result[13] = (track.samplerate >>> 16) & 0xFF; + result[14] = (track.samplerate >>> 8) & 0xFF; + result[15] = (track.samplerate) & 0xFF; + } + + return box(types.mdhd, result); +}; +mdia = function(track) { + return box(types.mdia, mdhd(track), hdlr(track.type), minf(track)); +}; +mfhd = function(sequenceNumber) { + return box(types.mfhd, new Uint8Array([ + 0x00, + 0x00, 0x00, 0x00, // flags + (sequenceNumber & 0xFF000000) >> 24, + (sequenceNumber & 0xFF0000) >> 16, + (sequenceNumber & 0xFF00) >> 8, + sequenceNumber & 0xFF, // sequence_number + ])); +}; +minf = function(track) { + return box(types.minf, + track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), + dinf(), + stbl(track)); +}; +moof = function(sequenceNumber, tracks) { + var + trackFragments = [], + i = tracks.length; + // build traf boxes for each track fragment + while (i--) { + trackFragments[i] = traf(tracks[i]); + } + return box.apply(null, [ + types.moof, + mfhd(sequenceNumber) + ].concat(trackFragments)); +}; +/** + * Returns a movie box. + * @param tracks {array} the tracks associated with this movie + * @see ISO/IEC 14496-12:2012(E), section 8.2.1 + */ +moov = function(tracks) { + var + i = tracks.length, + boxes = []; + + while (i--) { + boxes[i] = trak(tracks[i]); + } + + return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks))); +}; +mvex = function(tracks) { + var + i = tracks.length, + boxes = []; + + while (i--) { + boxes[i] = trex(tracks[i]); + } + return box.apply(null, [types.mvex].concat(boxes)); +}; +mvhd = function(duration) { + var + bytes = new Uint8Array([ + 0x00, // version 0 + 0x00, 0x00, 0x00, // flags + 0x00, 0x00, 0x00, 0x01, // creation_time + 0x00, 0x00, 0x00, 0x02, // modification_time + 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second + (duration & 0xFF000000) >> 24, + (duration & 0xFF0000) >> 16, + (duration & 0xFF00) >> 8, + duration & 0xFF, // duration + 0x00, 0x01, 0x00, 0x00, // 1.0 rate + 0x01, 0x00, // 1.0 volume + 0x00, 0x00, // reserved + 0x00, 0x00, 0x00, 0x00, // reserved + 0x00, 0x00, 0x00, 0x00, // reserved + 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, // pre_defined + 0xff, 0xff, 0xff, 0xff // next_track_ID + ]); + return box(types.mvhd, bytes); +}; + +sdtp = function(track) { + var + samples = track.samples || [], + bytes = new Uint8Array(4 + samples.length), + flags, + i; + + // leave the full box header (4 bytes) all zero + + // write the sample table + for (i = 0; i < samples.length; i++) { + flags = samples[i].flags; + + bytes[i + 4] = (flags.dependsOn << 4) | + (flags.isDependedOn << 2) | + (flags.hasRedundancy); + } + + return box(types.sdtp, + bytes); +}; + +stbl = function(track) { + return box(types.stbl, + stsd(track), + box(types.stts, STTS), + box(types.stsc, STSC), + box(types.stsz, STSZ), + box(types.stco, STCO)); +}; + +(function() { + var videoSample, audioSample; + + stsd = function(track) { + + return box(types.stsd, new Uint8Array([ + 0x00, // version 0 + 0x00, 0x00, 0x00, // flags + 0x00, 0x00, 0x00, 0x01 + ]), track.type === 'video' ? videoSample(track) : audioSample(track)); + }; + + videoSample = function(track) { + var + sps = track.sps || [], + pps = track.pps || [], + sequenceParameterSets = [], + pictureParameterSets = [], + i; + + // assemble the SPSs + for (i = 0; i < sps.length; i++) { + sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8); + sequenceParameterSets.push((sps[i].byteLength & 0xFF)); // sequenceParameterSetLength + sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS + } + + // assemble the PPSs + for (i = 0; i < pps.length; i++) { + pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8); + pictureParameterSets.push((pps[i].byteLength & 0xFF)); + pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i])); + } + + return box(types.avc1, new Uint8Array([ + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, // reserved + 0x00, 0x01, // data_reference_index + 0x00, 0x00, // pre_defined + 0x00, 0x00, // reserved + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, // pre_defined + (track.width & 0xff00) >> 8, + track.width & 0xff, // width + (track.height & 0xff00) >> 8, + track.height & 0xff, // height + 0x00, 0x48, 0x00, 0x00, // horizresolution + 0x00, 0x48, 0x00, 0x00, // vertresolution + 0x00, 0x00, 0x00, 0x00, // reserved + 0x00, 0x01, // frame_count + 0x13, + 0x76, 0x69, 0x64, 0x65, + 0x6f, 0x6a, 0x73, 0x2d, + 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x69, 0x62, 0x2d, + 0x68, 0x6c, 0x73, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, // compressorname + 0x00, 0x18, // depth = 24 + 0x11, 0x11 // pre_defined = -1 + ]), box(types.avcC, new Uint8Array([ + 0x01, // configurationVersion + track.profileIdc, // AVCProfileIndication + track.profileCompatibility, // profile_compatibility + track.levelIdc, // AVCLevelIndication + 0xff // lengthSizeMinusOne, hard-coded to 4 bytes + ].concat([ + sps.length // numOfSequenceParameterSets + ]).concat(sequenceParameterSets).concat([ + pps.length // numOfPictureParameterSets + ]).concat(pictureParameterSets))), // "PPS" + box(types.btrt, new Uint8Array([ + 0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB + 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate + 0x00, 0x2d, 0xc6, 0xc0 + ])) // avgBitrate + ); + }; + + audioSample = function(track) { + return box(types.mp4a, new Uint8Array([ + + // SampleEntry, ISO/IEC 14496-12 + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, // reserved + 0x00, 0x01, // data_reference_index + + // AudioSampleEntry, ISO/IEC 14496-12 + 0x00, 0x00, 0x00, 0x00, // reserved + 0x00, 0x00, 0x00, 0x00, // reserved + (track.channelcount & 0xff00) >> 8, + (track.channelcount & 0xff), // channelcount + + (track.samplesize & 0xff00) >> 8, + (track.samplesize & 0xff), // samplesize + 0x00, 0x00, // pre_defined + 0x00, 0x00, // reserved + + (track.samplerate & 0xff00) >> 8, + (track.samplerate & 0xff), + 0x00, 0x00 // samplerate, 16.16 + + // MP4AudioSampleEntry, ISO/IEC 14496-14 + ]), esds(track)); + }; +})(); + +styp = function() { + return box(types.styp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND); +}; + +tkhd = function(track) { + var result = new Uint8Array([ + 0x00, // version 0 + 0x00, 0x00, 0x07, // flags + 0x00, 0x00, 0x00, 0x00, // creation_time + 0x00, 0x00, 0x00, 0x00, // modification_time + (track.id & 0xFF000000) >> 24, + (track.id & 0xFF0000) >> 16, + (track.id & 0xFF00) >> 8, + track.id & 0xFF, // track_ID + 0x00, 0x00, 0x00, 0x00, // reserved + (track.duration & 0xFF000000) >> 24, + (track.duration & 0xFF0000) >> 16, + (track.duration & 0xFF00) >> 8, + track.duration & 0xFF, // duration + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, // reserved + 0x00, 0x00, // layer + 0x00, 0x00, // alternate_group + 0x01, 0x00, // non-audio track volume + 0x00, 0x00, // reserved + 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix + (track.width & 0xFF00) >> 8, + track.width & 0xFF, + 0x00, 0x00, // width + (track.height & 0xFF00) >> 8, + track.height & 0xFF, + 0x00, 0x00 // height + ]); + + return box(types.tkhd, result); +}; + +/** + * Generate a track fragment (traf) box. A traf box collects metadata + * about tracks in a movie fragment (moof) box. + */ +traf = function(track) { + var trackFragmentHeader, trackFragmentDecodeTime, + trackFragmentRun, sampleDependencyTable, dataOffset; + + trackFragmentHeader = box(types.tfhd, new Uint8Array([ + 0x00, // version 0 + 0x00, 0x00, 0x3a, // flags + (track.id & 0xFF000000) >> 24, + (track.id & 0xFF0000) >> 16, + (track.id & 0xFF00) >> 8, + (track.id & 0xFF), // track_ID + 0x00, 0x00, 0x00, 0x01, // sample_description_index + 0x00, 0x00, 0x00, 0x00, // default_sample_duration + 0x00, 0x00, 0x00, 0x00, // default_sample_size + 0x00, 0x00, 0x00, 0x00 // default_sample_flags + ])); + + trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([ + 0x00, // version 0 + 0x00, 0x00, 0x00, // flags + // baseMediaDecodeTime + (track.baseMediaDecodeTime >>> 24) & 0xFF, + (track.baseMediaDecodeTime >>> 16) & 0xFF, + (track.baseMediaDecodeTime >>> 8) & 0xFF, + track.baseMediaDecodeTime & 0xFF + ])); + + // the data offset specifies the number of bytes from the start of + // the containing moof to the first payload byte of the associated + // mdat + dataOffset = (32 + // tfhd + 16 + // tfdt + 8 + // traf header + 16 + // mfhd + 8 + // moof header + 8); // mdat header + + // audio tracks require less metadata + if (track.type === 'audio') { + trackFragmentRun = trun(track, dataOffset); + return box(types.traf, + trackFragmentHeader, + trackFragmentDecodeTime, + trackFragmentRun); + } + + // video tracks should contain an independent and disposable samples + // box (sdtp) + // generate one and adjust offsets to match + sampleDependencyTable = sdtp(track); + trackFragmentRun = trun(track, + sampleDependencyTable.length + dataOffset); + return box(types.traf, + trackFragmentHeader, + trackFragmentDecodeTime, + trackFragmentRun, + sampleDependencyTable); +}; + +/** + * Generate a track box. + * @param track {object} a track definition + * @return {Uint8Array} the track box + */ +trak = function(track) { + track.duration = track.duration || 0xffffffff; + return box(types.trak, + tkhd(track), + mdia(track)); +}; + +trex = function(track) { + var result = new Uint8Array([ + 0x00, // version 0 + 0x00, 0x00, 0x00, // flags + (track.id & 0xFF000000) >> 24, + (track.id & 0xFF0000) >> 16, + (track.id & 0xFF00) >> 8, + (track.id & 0xFF), // track_ID + 0x00, 0x00, 0x00, 0x01, // default_sample_description_index + 0x00, 0x00, 0x00, 0x00, // default_sample_duration + 0x00, 0x00, 0x00, 0x00, // default_sample_size + 0x00, 0x01, 0x00, 0x01 // default_sample_flags + ]); + // the last two bytes of default_sample_flags is the sample + // degradation priority, a hint about the importance of this sample + // relative to others. Lower the degradation priority for all sample + // types other than video. + if (track.type !== 'video') { + result[result.length - 1] = 0x00; + } + + return box(types.trex, result); +}; + +(function() { + var audioTrun, videoTrun, trunHeader; + + // This method assumes all samples are uniform. That is, if a + // duration is present for the first sample, it will be present for + // all subsequent samples. + // see ISO/IEC 14496-12:2012, Section 8.8.8.1 + trunHeader = function(samples, offset) { + var durationPresent = 0, sizePresent = 0, + flagsPresent = 0, compositionTimeOffset = 0; + + // trun flag constants + if (samples.length) { + if (samples[0].duration !== undefined) { + durationPresent = 0x1; + } + if (samples[0].size !== undefined) { + sizePresent = 0x2; + } + if (samples[0].flags !== undefined) { + flagsPresent = 0x4; + } + if (samples[0].compositionTimeOffset !== undefined) { + compositionTimeOffset = 0x8; + } + } + + return [ + 0x00, // version 0 + 0x00, + durationPresent | sizePresent | flagsPresent | compositionTimeOffset, + 0x01, // flags + (samples.length & 0xFF000000) >>> 24, + (samples.length & 0xFF0000) >>> 16, + (samples.length & 0xFF00) >>> 8, + samples.length & 0xFF, // sample_count + (offset & 0xFF000000) >>> 24, + (offset & 0xFF0000) >>> 16, + (offset & 0xFF00) >>> 8, + offset & 0xFF // data_offset + ]; + }; + + videoTrun = function(track, offset) { + var bytes, samples, sample, i; + + samples = track.samples || []; + offset += 8 + 12 + (16 * samples.length); + + bytes = trunHeader(samples, offset); + + for (i = 0; i < samples.length; i++) { + sample = samples[i]; + bytes = bytes.concat([ + (sample.duration & 0xFF000000) >>> 24, + (sample.duration & 0xFF0000) >>> 16, + (sample.duration & 0xFF00) >>> 8, + sample.duration & 0xFF, // sample_duration + (sample.size & 0xFF000000) >>> 24, + (sample.size & 0xFF0000) >>> 16, + (sample.size & 0xFF00) >>> 8, + sample.size & 0xFF, // sample_size + (sample.flags.isLeading << 2) | sample.flags.dependsOn, + (sample.flags.isDependedOn << 6) | + (sample.flags.hasRedundancy << 4) | + (sample.flags.paddingValue << 1) | + sample.flags.isNonSyncSample, + sample.flags.degradationPriority & 0xF0 << 8, + sample.flags.degradationPriority & 0x0F, // sample_flags + (sample.compositionTimeOffset & 0xFF000000) >>> 24, + (sample.compositionTimeOffset & 0xFF0000) >>> 16, + (sample.compositionTimeOffset & 0xFF00) >>> 8, + sample.compositionTimeOffset & 0xFF // sample_composition_time_offset + ]); + } + return box(types.trun, new Uint8Array(bytes)); + }; + + audioTrun = function(track, offset) { + var bytes, samples, sample, i; + + samples = track.samples || []; + offset += 8 + 12 + (8 * samples.length); + + bytes = trunHeader(samples, offset); + + for (i = 0; i < samples.length; i++) { + sample = samples[i]; + bytes = bytes.concat([ + (sample.duration & 0xFF000000) >>> 24, + (sample.duration & 0xFF0000) >>> 16, + (sample.duration & 0xFF00) >>> 8, + sample.duration & 0xFF, // sample_duration + (sample.size & 0xFF000000) >>> 24, + (sample.size & 0xFF0000) >>> 16, + (sample.size & 0xFF00) >>> 8, + sample.size & 0xFF]); // sample_size + } + + return box(types.trun, new Uint8Array(bytes)); + }; + + trun = function(track, offset) { + if (track.type === 'audio') { + return audioTrun(track, offset); + } else { + return videoTrun(track, offset); + } + }; +})(); + +module.exports = { + ftyp: ftyp, + mdat: mdat, + moof: moof, + moov: moov, + initSegment: function(tracks) { + var + fileType = ftyp(), + movie = moov(tracks), + result; + + result = new Uint8Array(fileType.byteLength + movie.byteLength); + result.set(fileType); + result.set(movie, fileType.byteLength); + return result; + } +}; + +},{}],87:[function(require,module,exports){ +/** + * mux.js + * + * Copyright (c) 2015 Brightcove + * All rights reserved. + * + * A stream-based mp2t to mp4 converter. This utility can be used to + * deliver mp4s to a SourceBuffer on platforms that support native + * Media Source Extensions. + */ +'use strict'; + +var Stream = require('../utils/stream.js'); +var mp4 = require('./mp4-generator.js'); +var m2ts = require('../m2ts/m2ts.js'); +var AdtsStream = require('../codecs/adts.js'); +var H264Stream = require('../codecs/h264').H264Stream; +var AacStream = require('../aac'); + +// constants +var AUDIO_PROPERTIES = [ + 'audioobjecttype', + 'channelcount', + 'samplerate', + 'samplingfrequencyindex', + 'samplesize' +]; + +var VIDEO_PROPERTIES = [ + 'width', + 'height', + 'profileIdc', + 'levelIdc', + 'profileCompatibility', +]; + +// object types +var VideoSegmentStream, AudioSegmentStream, Transmuxer, CoalesceStream; + +// Helper functions +var + createDefaultSample, + isLikelyAacData, + collectDtsInfo, + clearDtsInfo, + calculateTrackBaseMediaDecodeTime, + arrayEquals, + sumFrameByteLengths; + +/** + * Default sample object + * see ISO/IEC 14496-12:2012, section 8.6.4.3 + */ +createDefaultSample = function () { + return { + size: 0, + flags: { + isLeading: 0, + dependsOn: 1, + isDependedOn: 0, + hasRedundancy: 0, + degradationPriority: 0 + } + }; +}; + +isLikelyAacData = function (data) { + if ((data[0] === 'I'.charCodeAt(0)) && + (data[1] === 'D'.charCodeAt(0)) && + (data[2] === '3'.charCodeAt(0))) { + return true; + } + return false; +}; + +/** + * Compare two arrays (even typed) for same-ness + */ +arrayEquals = function(a, b) { + var + i; + + if (a.length !== b.length) { + return false; + } + + // compare the value of each element in the array + for (i = 0; i < a.length; i++) { + if (a[i] !== b[i]) { + return false; + } + } + + return true; +}; + +/** + * Sum the `byteLength` properties of the data in each AAC frame + */ +sumFrameByteLengths = function(array) { + var + i, + currentObj, + sum = 0; + + // sum the byteLength's all each nal unit in the frame + for (i = 0; i < array.length; i++) { + currentObj = array[i]; + sum += currentObj.data.byteLength; + } + + return sum; +}; + +/** + * Constructs a single-track, ISO BMFF media segment from AAC data + * events. The output of this stream can be fed to a SourceBuffer + * configured with a suitable initialization segment. + */ +AudioSegmentStream = function(track) { + var + adtsFrames = [], + sequenceNumber = 0, + earliestAllowedDts = 0; + + AudioSegmentStream.prototype.init.call(this); + + this.push = function(data) { + collectDtsInfo(track, data); + + if (track) { + AUDIO_PROPERTIES.forEach(function(prop) { + track[prop] = data[prop]; + }); + } + + // buffer audio data until end() is called + adtsFrames.push(data); + }; + + this.setEarliestDts = function(earliestDts) { + earliestAllowedDts = earliestDts - track.timelineStartInfo.baseMediaDecodeTime; + }; + + this.flush = function() { + var + frames, + moof, + mdat, + boxes; + + // return early if no audio data has been observed + if (adtsFrames.length === 0) { + this.trigger('done', 'AudioSegmentStream'); + return; + } + + frames = this.trimAdtsFramesByEarliestDts_(adtsFrames); + + // we have to build the index from byte locations to + // samples (that is, adts frames) in the audio data + track.samples = this.generateSampleTable_(frames); + + // concatenate the audio data to constuct the mdat + mdat = mp4.mdat(this.concatenateFrameData_(frames)); + + adtsFrames = []; + + calculateTrackBaseMediaDecodeTime(track); + moof = mp4.moof(sequenceNumber, [track]); + boxes = new Uint8Array(moof.byteLength + mdat.byteLength); + + // bump the sequence number for next time + sequenceNumber++; + + boxes.set(moof); + boxes.set(mdat, moof.byteLength); + + clearDtsInfo(track); + + this.trigger('data', {track: track, boxes: boxes}); + this.trigger('done', 'AudioSegmentStream'); + }; + + // If the audio segment extends before the earliest allowed dts + // value, remove AAC frames until starts at or after the earliest + // allowed DTS so that we don't end up with a negative baseMedia- + // DecodeTime for the audio track + this.trimAdtsFramesByEarliestDts_ = function(adtsFrames) { + if (track.minSegmentDts >= earliestAllowedDts) { + return adtsFrames; + } + + // We will need to recalculate the earliest segment Dts + track.minSegmentDts = Infinity; + + return adtsFrames.filter(function(currentFrame) { + // If this is an allowed frame, keep it and record it's Dts + if (currentFrame.dts >= earliestAllowedDts) { + track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts); + track.minSegmentPts = track.minSegmentDts; + return true; + } + // Otherwise, discard it + return false; + }); + }; + + // generate the track's raw mdat data from an array of frames + this.generateSampleTable_ = function(frames) { + var + i, + currentFrame, + samples = []; + + for (i = 0; i < frames.length; i++) { + currentFrame = frames[i]; + samples.push({ + size: currentFrame.data.byteLength, + duration: 1024 // For AAC audio, all samples contain 1024 samples + }); + } + return samples; + }; + + // generate the track's sample table from an array of frames + this.concatenateFrameData_ = function(frames) { + var + i, + currentFrame, + dataOffset = 0, + data = new Uint8Array(sumFrameByteLengths(frames)); + + for (i = 0; i < frames.length; i++) { + currentFrame = frames[i]; + + data.set(currentFrame.data, dataOffset); + dataOffset += currentFrame.data.byteLength; + } + return data; + }; +}; + +AudioSegmentStream.prototype = new Stream(); + +/** + * Constructs a single-track, ISO BMFF media segment from H264 data + * events. The output of this stream can be fed to a SourceBuffer + * configured with a suitable initialization segment. + * @param track {object} track metadata configuration + */ +VideoSegmentStream = function(track) { + var + sequenceNumber = 0, + nalUnits = [], + config, + pps; + + VideoSegmentStream.prototype.init.call(this); + + delete track.minPTS; + + this.gopCache_ = []; + + this.push = function(nalUnit) { + collectDtsInfo(track, nalUnit); + + // record the track config + if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) { + config = nalUnit.config; + track.sps = [nalUnit.data]; + + VIDEO_PROPERTIES.forEach(function(prop) { + track[prop] = config[prop]; + }, this); + } + + if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && + !pps) { + pps = nalUnit.data; + track.pps = [nalUnit.data]; + } + + // buffer video until flush() is called + nalUnits.push(nalUnit); + }; + + this.flush = function() { + var + frames, + gopForFusion, + gops, + moof, + mdat, + boxes; + + // Throw away nalUnits at the start of the byte stream until + // we find the first AUD + while (nalUnits.length) { + if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') { + break; + } + nalUnits.shift(); + } + + // Return early if no video data has been observed + if (nalUnits.length === 0) { + this.resetStream_(); + this.trigger('done', 'VideoSegmentStream'); + return; + } + + // Organize the raw nal-units into arrays that represent + // higher-level constructs such as frames and gops + // (group-of-pictures) + frames = this.groupNalsIntoFrames_(nalUnits); + gops = this.groupFramesIntoGops_(frames); + + // If the first frame of this fragment is not a keyframe we have + // a problem since MSE (on Chrome) requires a leading keyframe. + // + // We have two approaches to repairing this situation: + // 1) GOP-FUSION: + // This is where we keep track of the GOPS (group-of-pictures) + // from previous fragments and attempt to find one that we can + // prepend to the current fragment in order to create a valid + // fragment. + // 2) KEYFRAME-PULLING: + // Here we search for the first keyframe in the fragment and + // throw away all the frames between the start of the fragment + // and that keyframe. We then extend the duration and pull the + // PTS of the keyframe forward so that it covers the time range + // of the frames that were disposed of. + // + // #1 is far prefereable over #2 which can cause "stuttering" but + // requires more things to be just right. + if (!gops[0][0].keyFrame) { + // Search for a gop for fusion from our gopCache + gopForFusion = this.getGopForFusion_(nalUnits[0], track); + + if (gopForFusion) { + gops.unshift(gopForFusion); + // Adjust Gops' metadata to account for the inclusion of the + // new gop at the beginning + gops.byteLength += gopForFusion.byteLength; + gops.nalCount += gopForFusion.nalCount; + gops.pts = gopForFusion.pts; + gops.dts = gopForFusion.dts; + gops.duration += gopForFusion.duration; + } else { + // If we didn't find a candidate gop fall back to keyrame-pulling + gops = this.extendFirstKeyFrame_(gops); + } + } + collectDtsInfo(track, gops); + + // First, we have to build the index from byte locations to + // samples (that is, frames) in the video data + track.samples = this.generateSampleTable_(gops); + + // Concatenate the video data and construct the mdat + mdat = mp4.mdat(this.concatenateNalData_(gops)); + + // save all the nals in the last GOP into the gop cache + this.gopCache_.unshift({ + gop: gops.pop(), + pps: track.pps, + sps: track.sps + }); + + // Keep a maximum of 6 GOPs in the cache + this.gopCache_.length = Math.min(6, this.gopCache_.length); + + // Clear nalUnits + nalUnits = []; + + calculateTrackBaseMediaDecodeTime(track); + + this.trigger('timelineStartInfo', track.timelineStartInfo); + + moof = mp4.moof(sequenceNumber, [track]); + + // it would be great to allocate this array up front instead of + // throwing away hundreds of media segment fragments + boxes = new Uint8Array(moof.byteLength + mdat.byteLength); + + // Bump the sequence number for next time + sequenceNumber++; + + boxes.set(moof); + boxes.set(mdat, moof.byteLength); + + this.trigger('data', {track: track, boxes: boxes}); + + this.resetStream_(); + + // Continue with the flush process now + this.trigger('done', 'VideoSegmentStream'); + }; + + this.resetStream_ = function() { + clearDtsInfo(track); + + // reset config and pps because they may differ across segments + // for instance, when we are rendition switching + config = undefined; + pps = undefined; + }; + + // Search for a candidate Gop for gop-fusion from the gop cache and + // return it or return null if no good candidate was found + this.getGopForFusion_ = function (nalUnit) { + var + halfSecond = 45000, // Half-a-second in a 90khz clock + allowableOverlap = 10000, // About 3 frames @ 30fps + nearestDistance = Infinity, + dtsDistance, + nearestGopObj, + currentGop, + currentGopObj, + i; + + // Search for the GOP nearest to the beginning of this nal unit + for (i = 0; i < this.gopCache_.length; i++) { + currentGopObj = this.gopCache_[i]; + currentGop = currentGopObj.gop; + + // Reject Gops with different SPS or PPS + if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || + !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) { + continue; + } + + // Reject Gops that would require a negative baseMediaDecodeTime + if (currentGop.dts < track.timelineStartInfo.dts) { + continue; + } + + // The distance between the end of the gop and the start of the nalUnit + dtsDistance = (nalUnit.dts - currentGop.dts) - currentGop.duration; + + // Only consider GOPS that start before the nal unit and end within + // a half-second of the nal unit + if (dtsDistance >= -allowableOverlap && + dtsDistance <= halfSecond) { + + // Always use the closest GOP we found if there is more than + // one candidate + if (!nearestGopObj || + nearestDistance > dtsDistance) { + nearestGopObj = currentGopObj; + nearestDistance = dtsDistance; + } + } + } + + if (nearestGopObj) { + return nearestGopObj.gop; + } + return null; + }; + + this.extendFirstKeyFrame_ = function(gops) { + var + h, i, + currentGop, + newGops; + + if (!gops[0][0].keyFrame) { + // Remove the first GOP + currentGop = gops.shift(); + + gops.byteLength -= currentGop.byteLength; + gops.nalCount -= currentGop.nalCount; + + // Extend the first frame of what is now the + // first gop to cover the time period of the + // frames we just removed + gops[0][0].dts = currentGop.dts; + gops[0][0].pts = currentGop.pts; + gops[0][0].duration += currentGop.duration; + } + + return gops; + }; + + // Convert an array of nal units into an array of frames with each frame being + // composed of the nal units that make up that frame + // Also keep track of cummulative data about the frame from the nal units such + // as the frame duration, starting pts, etc. + this.groupNalsIntoFrames_ = function(nalUnits) { + var + i, + currentNal, + startPts, + startDts, + currentFrame = [], + frames = []; + + currentFrame.byteLength = 0; + + for (i = 0; i < nalUnits.length; i++) { + currentNal = nalUnits[i]; + + // Split on 'aud'-type nal units + if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') { + // Since the very first nal unit is expected to be an AUD + // only push to the frames array when currentFrame is not empty + if (currentFrame.length) { + currentFrame.duration = currentNal.dts - currentFrame.dts; + frames.push(currentFrame); + } + currentFrame = [currentNal]; + currentFrame.byteLength = currentNal.data.byteLength; + currentFrame.pts = currentNal.pts; + currentFrame.dts = currentNal.dts; + } else { + // Specifically flag key frames for ease of use later + if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') { + currentFrame.keyFrame = true; + } + currentFrame.duration = currentNal.dts - currentFrame.dts; + currentFrame.byteLength += currentNal.data.byteLength; + currentFrame.push(currentNal); + } + } + + // For the last frame, use the duration of the previous frame if we + // have nothing better to go on + if (frames.length && + (!currentFrame.duration || + currentFrame.duration <= 0)) { + currentFrame.duration = frames[frames.length - 1].duration; + } + + // Push the final frame + frames.push(currentFrame); + return frames; + }; + + // Convert an array of frames into an array of Gop with each Gop being composed + // of the frames that make up that Gop + // Also keep track of cummulative data about the Gop from the frames such as the + // Gop duration, starting pts, etc. + this.groupFramesIntoGops_ = function(frames) { + var + i, + currentFrame, + currentGop = [], + gops = []; + + // We must pre-set some of the values on the Gop since we + // keep running totals of these values + currentGop.byteLength = 0; + currentGop.nalCount = 0; + currentGop.duration = 0; + currentGop.pts = frames[0].pts; + currentGop.dts = frames[0].dts; + + // store some metadata about all the Gops + gops.byteLength = 0; + gops.nalCount = 0; + gops.duration = 0; + gops.pts = frames[0].pts; + gops.dts = frames[0].dts; + + for (i = 0; i < frames.length; i++) { + currentFrame = frames[i]; + + if (currentFrame.keyFrame) { + // Since the very first frame is expected to be an keyframe + // only push to the gops array when currentGop is not empty + if (currentGop.length) { + gops.push(currentGop); + gops.byteLength += currentGop.byteLength; + gops.nalCount += currentGop.nalCount; + gops.duration += currentGop.duration; + } + + currentGop = [currentFrame]; + currentGop.nalCount = currentFrame.length; + currentGop.byteLength = currentFrame.byteLength; + currentGop.pts = currentFrame.pts; + currentGop.dts = currentFrame.dts; + currentGop.duration = currentFrame.duration; + } else { + currentGop.duration += currentFrame.duration; + currentGop.nalCount += currentFrame.length; + currentGop.byteLength += currentFrame.byteLength; + currentGop.push(currentFrame); + } + } + + if (gops.length && currentGop.duration <= 0) { + currentGop.duration = gops[gops.length - 1].duration; + } + gops.byteLength += currentGop.byteLength; + gops.nalCount += currentGop.nalCount; + gops.duration += currentGop.duration; + + // push the final Gop + gops.push(currentGop); + return gops; + }; + + // generate the track's sample table from an array of gops + this.generateSampleTable_ = function(gops, baseDataOffset) { + var + h, i, + sample, + currentGop, + currentFrame, + currentSample, + dataOffset = baseDataOffset || 0, + samples = []; + + for (h = 0; h < gops.length; h++) { + currentGop = gops[h]; + + for (i = 0; i < currentGop.length; i++) { + currentFrame = currentGop[i]; + + sample = createDefaultSample(); + + sample.dataOffset = dataOffset; + sample.compositionTimeOffset = currentFrame.pts - currentFrame.dts; + sample.duration = currentFrame.duration; + sample.size = 4 * currentFrame.length; // Space for nal unit size + sample.size += currentFrame.byteLength; + + if (currentFrame.keyFrame) { + sample.flags.dependsOn = 2; + } + + dataOffset += sample.size; + + samples.push(sample); + } + } + return samples; + }; + + // generate the track's raw mdat data from an array of gops + this.concatenateNalData_ = function (gops) { + var + h, i, j, + currentGop, + currentFrame, + currentNal, + dataOffset = 0, + nalsByteLength = gops.byteLength, + numberOfNals = gops.nalCount, + totalByteLength = nalsByteLength + 4 * numberOfNals, + data = new Uint8Array(totalByteLength), + view = new DataView(data.buffer); + + // For each Gop.. + for (h = 0; h < gops.length; h++) { + currentGop = gops[h]; + + // For each Frame.. + for (i = 0; i < currentGop.length; i++) { + currentFrame = currentGop[i]; + + // For each NAL.. + for (j = 0; j < currentFrame.length; j++) { + currentNal = currentFrame[j]; + + view.setUint32(dataOffset, currentNal.data.byteLength); + dataOffset += 4; + data.set(currentNal.data, dataOffset); + dataOffset += currentNal.data.byteLength; + } + } + } + return data; + }; +}; + +VideoSegmentStream.prototype = new Stream(); + +/** + * Store information about the start and end of the track and the + * duration for each frame/sample we process in order to calculate + * the baseMediaDecodeTime + */ +collectDtsInfo = function (track, data) { + if (typeof data.pts === 'number') { + if (track.timelineStartInfo.pts === undefined) { + track.timelineStartInfo.pts = data.pts; + } + + if (track.minSegmentPts === undefined) { + track.minSegmentPts = data.pts; + } else { + track.minSegmentPts = Math.min(track.minSegmentPts, data.pts); + } + + if (track.maxSegmentPts === undefined) { + track.maxSegmentPts = data.pts; + } else { + track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts); + } + } + + if (typeof data.dts === 'number') { + if (track.timelineStartInfo.dts === undefined) { + track.timelineStartInfo.dts = data.dts; + } + + if (track.minSegmentDts === undefined) { + track.minSegmentDts = data.dts; + } else { + track.minSegmentDts = Math.min(track.minSegmentDts, data.dts); + } + + if (track.maxSegmentDts === undefined) { + track.maxSegmentDts = data.dts; + } else { + track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts); + } + } +}; + +/** + * Clear values used to calculate the baseMediaDecodeTime between + * tracks + */ +clearDtsInfo = function (track) { + delete track.minSegmentDts; + delete track.maxSegmentDts; + delete track.minSegmentPts; + delete track.maxSegmentPts; +}; + +/** + * Calculate the track's baseMediaDecodeTime based on the earliest + * DTS the transmuxer has ever seen and the minimum DTS for the + * current track + */ +calculateTrackBaseMediaDecodeTime = function (track) { + var + oneSecondInPTS = 90000, // 90kHz clock + scale, + // Calculate the distance, in time, that this segment starts from the start + // of the timeline (earliest time seen since the transmuxer initialized) + timeSinceStartOfTimeline = track.minSegmentDts - track.timelineStartInfo.dts, + // Calculate the first sample's effective compositionTimeOffset + firstSampleCompositionOffset = track.minSegmentPts - track.minSegmentDts; + + // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where + // we want the start of the first segment to be placed + track.baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; + + // Add to that the distance this segment is from the very first + track.baseMediaDecodeTime += timeSinceStartOfTimeline; + + // Subtract this segment's "compositionTimeOffset" so that the first frame of + // this segment is displayed exactly at the `baseMediaDecodeTime` or at the + // end of the previous segment + track.baseMediaDecodeTime -= firstSampleCompositionOffset; + + // baseMediaDecodeTime must not become negative + track.baseMediaDecodeTime = Math.max(0, track.baseMediaDecodeTime); + + if (track.type === 'audio') { + // Audio has a different clock equal to the sampling_rate so we need to + // scale the PTS values into the clock rate of the track + scale = track.samplerate / oneSecondInPTS; + track.baseMediaDecodeTime *= scale; + track.baseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime); + } +}; + +/** + * A Stream that can combine multiple streams (ie. audio & video) + * into a single output segment for MSE. Also supports audio-only + * and video-only streams. + */ +CoalesceStream = function(options, metadataStream) { + // Number of Tracks per output segment + // If greater than 1, we combine multiple + // tracks into a single segment + this.numberOfTracks = 0; + this.metadataStream = metadataStream; + + if (typeof options.remux !== 'undefined') { + this.remuxTracks = !!options.remux; + } else { + this.remuxTracks = true; + } + + this.pendingTracks = []; + this.videoTrack = null; + this.pendingBoxes = []; + this.pendingCaptions = []; + this.pendingMetadata = []; + this.pendingBytes = 0; + this.emittedTracks = 0; + + CoalesceStream.prototype.init.call(this); + + // Take output from multiple + this.push = function(output) { + // buffer incoming captions until the associated video segment + // finishes + if (output.text) { + return this.pendingCaptions.push(output); + } + // buffer incoming id3 tags until the final flush + if (output.frames) { + return this.pendingMetadata.push(output); + } + + // Add this track to the list of pending tracks and store + // important information required for the construction of + // the final segment + this.pendingTracks.push(output.track); + this.pendingBoxes.push(output.boxes); + this.pendingBytes += output.boxes.byteLength; + + if (output.track.type === 'video') { + this.videoTrack = output.track; + } + if (output.track.type === 'audio') { + this.audioTrack = output.track; + } + }; +}; + +CoalesceStream.prototype = new Stream(); +CoalesceStream.prototype.flush = function(flushSource) { + var + offset = 0, + event = { + captions: [], + metadata: [], + info: {} + }, + caption, + id3, + initSegment, + timelineStartPts = 0, + i; + + if (this.pendingTracks.length < this.numberOfTracks) { + if (flushSource !== 'VideoSegmentStream' && + flushSource !== 'AudioSegmentStream') { + // Return because we haven't received a flush from a data-generating + // portion of the segment (meaning that we have only recieved meta-data + // or captions.) + return; + } else if (this.remuxTracks) { + // Return until we have enough tracks from the pipeline to remux (if we + // are remuxing audio and video into a single MP4) + return; + } else if (this.pendingTracks.length === 0) { + // In the case where we receive a flush without any data having been + // received we consider it an emitted track for the purposes of coalescing + // `done` events. + // We do this for the case where there is an audio and video track in the + // segment but no audio data. (seen in several playlists with alternate + // audio tracks and no audio present in the main TS segments.) + this.emittedTracks++; + + if (this.emittedTracks >= this.numberOfTracks) { + this.trigger('done'); + this.emittedTracks = 0; + } + return; + } + } + + if (this.videoTrack) { + timelineStartPts = this.videoTrack.timelineStartInfo.pts; + VIDEO_PROPERTIES.forEach(function(prop) { + event.info[prop] = this.videoTrack[prop]; + }, this); + } else if (this.audioTrack) { + timelineStartPts = this.audioTrack.timelineStartInfo.pts; + AUDIO_PROPERTIES.forEach(function(prop) { + event.info[prop] = this.audioTrack[prop]; + }, this); + } + + if (this.pendingTracks.length === 1) { + event.type = this.pendingTracks[0].type; + } else { + event.type = 'combined'; + } + + this.emittedTracks += this.pendingTracks.length; + + initSegment = mp4.initSegment(this.pendingTracks); + + // Create a new typed array large enough to hold the init + // segment and all tracks + event.data = new Uint8Array(initSegment.byteLength + + this.pendingBytes); + + // Create an init segment containing a moov + // and track definitions + event.data.set(initSegment); + offset += initSegment.byteLength; + + // Append each moof+mdat (one per track) after the init segment + for (i = 0; i < this.pendingBoxes.length; i++) { + event.data.set(this.pendingBoxes[i], offset); + offset += this.pendingBoxes[i].byteLength; + } + + // Translate caption PTS times into second offsets into the + // video timeline for the segment + for (i = 0; i < this.pendingCaptions.length; i++) { + caption = this.pendingCaptions[i]; + caption.startTime = (caption.startPts - timelineStartPts); + caption.startTime /= 90e3; + caption.endTime = (caption.endPts - timelineStartPts); + caption.endTime /= 90e3; + event.captions.push(caption); + } + + // Translate ID3 frame PTS times into second offsets into the + // video timeline for the segment + for (i = 0; i < this.pendingMetadata.length; i++) { + id3 = this.pendingMetadata[i]; + id3.cueTime = (id3.pts - timelineStartPts); + id3.cueTime /= 90e3; + event.metadata.push(id3); + } + // We add this to every single emitted segment even though we only need + // it for the first + event.metadata.dispatchType = this.metadataStream.dispatchType; + + // Reset stream state + this.pendingTracks.length = 0; + this.videoTrack = null; + this.pendingBoxes.length = 0; + this.pendingCaptions.length = 0; + this.pendingBytes = 0; + this.pendingMetadata.length = 0; + + // Emit the built segment + this.trigger('data', event); + + // Only emit `done` if all tracks have been flushed and emitted + if (this.emittedTracks >= this.numberOfTracks) { + this.trigger('done'); + this.emittedTracks = 0; + } +}; +/** + * A Stream that expects MP2T binary data as input and produces + * corresponding media segments, suitable for use with Media Source + * Extension (MSE) implementations that support the ISO BMFF byte + * stream format, like Chrome. + */ +Transmuxer = function(options) { + var + self = this, + hasFlushed = true, + videoTrack, + audioTrack; + + Transmuxer.prototype.init.call(this); + + options = options || {}; + this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0; + this.transmuxPipeline_ = {}; + + this.setupAacPipeline = function() { + var pipeline = {}; + this.transmuxPipeline_ = pipeline; + + pipeline.type = 'aac'; + pipeline.metadataStream = new m2ts.MetadataStream(); + + // set up the parsing pipeline + pipeline.aacStream = new AacStream(); + pipeline.adtsStream = new AdtsStream(); + pipeline.coalesceStream = new CoalesceStream(options, pipeline.metadataStream); + pipeline.headOfPipeline = pipeline.aacStream; + + pipeline.aacStream.pipe(pipeline.adtsStream); + pipeline.aacStream.pipe(pipeline.metadataStream); + pipeline.metadataStream.pipe(pipeline.coalesceStream); + + pipeline.metadataStream.on('timestamp', function(frame) { + pipeline.aacStream.setTimestamp(frame.timeStamp); + }); + + pipeline.aacStream.on('data', function(data) { + var i; + + if (data.type === 'timed-metadata' && !pipeline.audioSegmentStream) { + audioTrack = audioTrack || { + timelineStartInfo: { + baseMediaDecodeTime: self.baseMediaDecodeTime + }, + codec: 'adts', + type: 'audio' + }; + // hook up the audio segment stream to the first track with aac data + pipeline.coalesceStream.numberOfTracks++; + pipeline.audioSegmentStream = new AudioSegmentStream(audioTrack); + // Set up the final part of the audio pipeline + pipeline.adtsStream + .pipe(pipeline.audioSegmentStream) + .pipe(pipeline.coalesceStream); + } + }); + + // Re-emit any data coming from the coalesce stream to the outside world + pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); + // Let the consumer know we have finished flushing the entire pipeline + pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done')); + }; + + this.setupTsPipeline = function() { + var pipeline = {}; + this.transmuxPipeline_ = pipeline; + + pipeline.type = 'ts'; + pipeline.metadataStream = new m2ts.MetadataStream(); + + // set up the parsing pipeline + pipeline.packetStream = new m2ts.TransportPacketStream(); + pipeline.parseStream = new m2ts.TransportParseStream(); + pipeline.elementaryStream = new m2ts.ElementaryStream(); + pipeline.adtsStream = new AdtsStream(); + pipeline.h264Stream = new H264Stream(); + pipeline.captionStream = new m2ts.CaptionStream(); + pipeline.coalesceStream = new CoalesceStream(options, pipeline.metadataStream); + pipeline.headOfPipeline = pipeline.packetStream; + + // disassemble MPEG2-TS packets into elementary streams + pipeline.packetStream + .pipe(pipeline.parseStream) + .pipe(pipeline.elementaryStream); + + // !!THIS ORDER IS IMPORTANT!! + // demux the streams + pipeline.elementaryStream + .pipe(pipeline.h264Stream); + pipeline.elementaryStream + .pipe(pipeline.adtsStream); + + pipeline.elementaryStream + .pipe(pipeline.metadataStream) + .pipe(pipeline.coalesceStream); + + // Hook up CEA-608/708 caption stream + pipeline.h264Stream.pipe(pipeline.captionStream) + .pipe(pipeline.coalesceStream); + + pipeline.elementaryStream.on('data', function(data) { + var i; + + if (data.type === 'metadata') { + i = data.tracks.length; + + // scan the tracks listed in the metadata + while (i--) { + if (!videoTrack && data.tracks[i].type === 'video') { + videoTrack = data.tracks[i]; + videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime; + } else if (!audioTrack && data.tracks[i].type === 'audio') { + audioTrack = data.tracks[i]; + audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime; + } + } + + // hook up the video segment stream to the first track with h264 data + if (videoTrack && !pipeline.videoSegmentStream) { + pipeline.coalesceStream.numberOfTracks++; + pipeline.videoSegmentStream = new VideoSegmentStream(videoTrack); + + pipeline.videoSegmentStream.on('timelineStartInfo', function(timelineStartInfo){ + // When video emits timelineStartInfo data after a flush, we forward that + // info to the AudioSegmentStream, if it exists, because video timeline + // data takes precedence. + if (audioTrack) { + audioTrack.timelineStartInfo = timelineStartInfo; + // On the first segment we trim AAC frames that exist before the + // very earliest DTS we have seen in video because Chrome will + // interpret any video track with a baseMediaDecodeTime that is + // non-zero as a gap. + pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts); + } + }); + + // Set up the final part of the video pipeline + pipeline.h264Stream + .pipe(pipeline.videoSegmentStream) + .pipe(pipeline.coalesceStream); + } + + if (audioTrack && !pipeline.audioSegmentStream) { + // hook up the audio segment stream to the first track with aac data + pipeline.coalesceStream.numberOfTracks++; + pipeline.audioSegmentStream = new AudioSegmentStream(audioTrack); + + // Set up the final part of the audio pipeline + pipeline.adtsStream + .pipe(pipeline.audioSegmentStream) + .pipe(pipeline.coalesceStream); + } + } + }); + + // Re-emit any data coming from the coalesce stream to the outside world + pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); + // Let the consumer know we have finished flushing the entire pipeline + pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done')); + }; + + // hook up the segment streams once track metadata is delivered + this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) { + var pipeline = this.transmuxPipeline_; + + this.baseMediaDecodeTime = baseMediaDecodeTime; + if (audioTrack) { + audioTrack.timelineStartInfo.dts = undefined; + audioTrack.timelineStartInfo.pts = undefined; + clearDtsInfo(audioTrack); + audioTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime; + } + if (videoTrack) { + if (pipeline.videoSegmentStream) { + pipeline.videoSegmentStream.gopCache_ = []; + } + videoTrack.timelineStartInfo.dts = undefined; + videoTrack.timelineStartInfo.pts = undefined; + clearDtsInfo(videoTrack); + videoTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime; + } + }; + + // feed incoming data to the front of the parsing pipeline + this.push = function(data) { + if (hasFlushed) { + var isAac = isLikelyAacData(data); + + if (isAac && this.transmuxPipeline_.type !== 'aac') { + this.setupAacPipeline(); + } else if (!isAac && this.transmuxPipeline_.type !== 'ts') { + this.setupTsPipeline(); + } + hasFlushed = false; + } + this.transmuxPipeline_.headOfPipeline.push(data); + }; + + // flush any buffered data + this.flush = function() { + hasFlushed = true; + // Start at the top of the pipeline and flush all pending work + this.transmuxPipeline_.headOfPipeline.flush(); + }; +}; +Transmuxer.prototype = new Stream(); + +module.exports = { + Transmuxer: Transmuxer, + VideoSegmentStream: VideoSegmentStream, + AudioSegmentStream: AudioSegmentStream, + AUDIO_PROPERTIES: AUDIO_PROPERTIES, + VIDEO_PROPERTIES: VIDEO_PROPERTIES +}; + +},{"../aac":75,"../codecs/adts.js":76,"../codecs/h264":77,"../m2ts/m2ts.js":82,"../utils/stream.js":89,"./mp4-generator.js":86}],88:[function(require,module,exports){ +'use strict'; + +var ExpGolomb; + +/** + * Parser for exponential Golomb codes, a variable-bitwidth number encoding + * scheme used by h264. + */ +ExpGolomb = function(workingData) { + var + // the number of bytes left to examine in workingData + workingBytesAvailable = workingData.byteLength, + + // the current word being examined + workingWord = 0, // :uint + + // the number of bits left to examine in the current word + workingBitsAvailable = 0; // :uint; + + // ():uint + this.length = function() { + return (8 * workingBytesAvailable); + }; + + // ():uint + this.bitsAvailable = function() { + return (8 * workingBytesAvailable) + workingBitsAvailable; + }; + + // ():void + this.loadWord = function() { + var + position = workingData.byteLength - workingBytesAvailable, + workingBytes = new Uint8Array(4), + availableBytes = Math.min(4, workingBytesAvailable); + + if (availableBytes === 0) { + throw new Error('no bytes available'); + } + + workingBytes.set(workingData.subarray(position, + position + availableBytes)); + workingWord = new DataView(workingBytes.buffer).getUint32(0); + + // track the amount of workingData that has been processed + workingBitsAvailable = availableBytes * 8; + workingBytesAvailable -= availableBytes; + }; + + // (count:int):void + this.skipBits = function(count) { + var skipBytes; // :int + if (workingBitsAvailable > count) { + workingWord <<= count; + workingBitsAvailable -= count; + } else { + count -= workingBitsAvailable; + skipBytes = Math.floor(count / 8); + + count -= (skipBytes * 8); + workingBytesAvailable -= skipBytes; + + this.loadWord(); + + workingWord <<= count; + workingBitsAvailable -= count; + } + }; + + // (size:int):uint + this.readBits = function(size) { + var + bits = Math.min(workingBitsAvailable, size), // :uint + valu = workingWord >>> (32 - bits); // :uint + // if size > 31, handle error + workingBitsAvailable -= bits; + if (workingBitsAvailable > 0) { + workingWord <<= bits; + } else if (workingBytesAvailable > 0) { + this.loadWord(); + } + + bits = size - bits; + if (bits > 0) { + return valu << bits | this.readBits(bits); + } else { + return valu; + } + }; + + // ():uint + this.skipLeadingZeros = function() { + var leadingZeroCount; // :uint + for (leadingZeroCount = 0 ; leadingZeroCount < workingBitsAvailable ; ++leadingZeroCount) { + if (0 !== (workingWord & (0x80000000 >>> leadingZeroCount))) { + // the first bit of working word is 1 + workingWord <<= leadingZeroCount; + workingBitsAvailable -= leadingZeroCount; + return leadingZeroCount; + } + } + + // we exhausted workingWord and still have not found a 1 + this.loadWord(); + return leadingZeroCount + this.skipLeadingZeros(); + }; + + // ():void + this.skipUnsignedExpGolomb = function() { + this.skipBits(1 + this.skipLeadingZeros()); + }; + + // ():void + this.skipExpGolomb = function() { + this.skipBits(1 + this.skipLeadingZeros()); + }; + + // ():uint + this.readUnsignedExpGolomb = function() { + var clz = this.skipLeadingZeros(); // :uint + return this.readBits(clz + 1) - 1; + }; + + // ():int + this.readExpGolomb = function() { + var valu = this.readUnsignedExpGolomb(); // :int + if (0x01 & valu) { + // the number is odd if the low order bit is set + return (1 + valu) >>> 1; // add 1 to make it even, and divide by 2 + } else { + return -1 * (valu >>> 1); // divide by two then make it negative + } + }; + + // Some convenience functions + // :Boolean + this.readBoolean = function() { + return 1 === this.readBits(1); + }; + + // ():int + this.readUnsignedByte = function() { + return this.readBits(8); + }; + + this.loadWord(); +}; + +module.exports = ExpGolomb; + +},{}],89:[function(require,module,exports){ +/** + * mux.js + * + * Copyright (c) 2014 Brightcove + * All rights reserved. + * + * A lightweight readable stream implemention that handles event dispatching. + * Objects that inherit from streams should call init in their constructors. + */ +'use strict'; + +var Stream = function() { + this.init = function() { + var listeners = {}; + /** + * Add a listener for a specified event type. + * @param type {string} the event name + * @param listener {function} the callback to be invoked when an event of + * the specified type occurs + */ + this.on = function(type, listener) { + if (!listeners[type]) { + listeners[type] = []; + } + listeners[type].push(listener); + }; + /** + * Remove a listener for a specified event type. + * @param type {string} the event name + * @param listener {function} a function previously registered for this + * type of event through `on` + */ + this.off = function(type, listener) { + var index; + if (!listeners[type]) { + return false; + } + index = listeners[type].indexOf(listener); + listeners[type].splice(index, 1); + return index > -1; + }; + /** + * Trigger an event of the specified type on this stream. Any additional + * arguments to this function are passed as parameters to event listeners. + * @param type {string} the event name + */ + this.trigger = function(type) { + var callbacks, i, length, args; + callbacks = listeners[type]; + if (!callbacks) { + return; + } + // Slicing the arguments on every invocation of this method + // can add a significant amount of overhead. Avoid the + // intermediate object creation for the common case of a + // single callback argument + if (arguments.length === 2) { + length = callbacks.length; + for (i = 0; i < length; ++i) { + callbacks[i].call(this, arguments[1]); + } + } else { + args = []; + i = arguments.length; + for (i = 1; i < arguments.length; ++i) { + args.push(arguments[i]); + } + length = callbacks.length; + for (i = 0; i < length; ++i) { + callbacks[i].apply(this, args); + } + } + }; + /** + * Destroys the stream and cleans up. + */ + this.dispose = function() { + listeners = {}; + }; + }; +}; + +/** + * Forwards all `data` events on this stream to the destination stream. The + * destination stream should provide a method `push` to receive the data + * events as they arrive. + * @param destination {stream} the stream that will receive all `data` events + * @param autoFlush {boolean} if false, we will not call `flush` on the destination + * when the current stream emits a 'done' event + * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options + */ +Stream.prototype.pipe = function(destination) { + this.on('data', function(data) { + destination.push(data); + }); + + this.on('done', function(flushSource) { + destination.flush(flushSource); + }); + + return destination; +}; + +// Default stream functions that are expected to be overridden to perform +// actual work. These are provided by the prototype as a sort of no-op +// implementation so that we don't have to check for their existence in the +// `pipe` function above. +Stream.prototype.push = function(data) { + this.trigger('data', data); +}; + +Stream.prototype.flush = function(flushSource) { + this.trigger('done', flushSource); +}; + +module.exports = Stream; + +},{}],90:[function(require,module,exports){ +var bundleFn = arguments[3]; +var sources = arguments[4]; +var cache = arguments[5]; + +var stringify = JSON.stringify; + +module.exports = function (fn) { + var keys = []; + var wkey; + var cacheKeys = Object.keys(cache); + + for (var i = 0, l = cacheKeys.length; i < l; i++) { + var key = cacheKeys[i]; + if (cache[key].exports === fn) { + wkey = key; + break; + } + } + + if (!wkey) { + wkey = Math.floor(Math.pow(16, 8) * Math.random()).toString(16); + var wcache = {}; + for (var i = 0, l = cacheKeys.length; i < l; i++) { + var key = cacheKeys[i]; + wcache[key] = key; + } + sources[wkey] = [ + Function(['require','module','exports'], '(' + fn + ')(self)'), + wcache + ]; + } + var skey = Math.floor(Math.pow(16, 8) * Math.random()).toString(16); + + var scache = {}; scache[wkey] = wkey; + sources[skey] = [ + Function(['require'],'require(' + stringify(wkey) + ')(self)'), + scache + ]; + + var src = '(' + bundleFn + ')({' + + Object.keys(sources).map(function (key) { + return stringify(key) + ':[' + + sources[key][0] + + ',' + stringify(sources[key][1]) + ']' + ; + }).join(',') + + '},{},[' + stringify(skey) + '])' + ; + + var URL = window.URL || window.webkitURL || window.mozURL || window.msURL; + + return new Worker(URL.createObjectURL( + new Blob([src], { type: 'text/javascript' }) + )); +}; + +},{}],91:[function(require,module,exports){ +(function (global){ +/** + * @file videojs-contrib-hls.js + * + * The main file for the HLS project. + * License: https://github.com/videojs/videojs-contrib-hls/blob/master/LICENSE + */ +'use strict'; + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var _globalDocument = require('global/document'); + +var _globalDocument2 = _interopRequireDefault(_globalDocument); + +var _playlistLoader = require('./playlist-loader'); + +var _playlistLoader2 = _interopRequireDefault(_playlistLoader); + +var _playlist = require('./playlist'); + +var _playlist2 = _interopRequireDefault(_playlist); + +var _xhr = require('./xhr'); + +var _xhr2 = _interopRequireDefault(_xhr); + +var _decrypter = require('./decrypter'); + +var _binUtils = require('./bin-utils'); + +var _binUtils2 = _interopRequireDefault(_binUtils); + +var _videojsContribMediaSources = require('videojs-contrib-media-sources'); + +var _m3u8Parser = require('m3u8-parser'); + +var _m3u8Parser2 = _interopRequireDefault(_m3u8Parser); + +var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); + +var _videoJs2 = _interopRequireDefault(_videoJs); + +var _masterPlaylistController = require('./master-playlist-controller'); + +var _masterPlaylistController2 = _interopRequireDefault(_masterPlaylistController); + +var _config = require('./config'); + +var _config2 = _interopRequireDefault(_config); + +var _renditionMixin = require('./rendition-mixin'); + +var _renditionMixin2 = _interopRequireDefault(_renditionMixin); + +/** + * determine if an object a is differnt from + * and object b. both only having one dimensional + * properties + * + * @param {Object} a object one + * @param {Object} b object two + * @return {Boolean} if the object has changed or not + */ +var objectChanged = function objectChanged(a, b) { + if (typeof a !== typeof b) { + return true; + } + // if we have a different number of elements + // something has changed + if (Object.keys(a).length !== Object.keys(b).length) { + return true; + } + + for (var prop in a) { + if (!b[prop] || a[prop] !== b[prop]) { + return true; + } + } + return false; +}; + +var Hls = { + PlaylistLoader: _playlistLoader2['default'], + Playlist: _playlist2['default'], + Decrypter: _decrypter.Decrypter, + AsyncStream: _decrypter.AsyncStream, + decrypt: _decrypter.decrypt, + utils: _binUtils2['default'], + xhr: (0, _xhr2['default'])() +}; + +Object.defineProperty(Hls, 'GOAL_BUFFER_LENGTH', { + get: function get() { + _videoJs2['default'].log.warn('using Hls.GOAL_BUFFER_LENGTH is UNSAFE be sure ' + 'you know what you are doing'); + return _config2['default'].GOAL_BUFFER_LENGTH; + }, + set: function set(v) { + _videoJs2['default'].log.warn('using Hls.GOAL_BUFFER_LENGTH is UNSAFE be sure ' + 'you know what you are doing'); + if (typeof v !== 'number' || v <= 0) { + _videoJs2['default'].log.warn('value passed to Hls.GOAL_BUFFER_LENGTH ' + 'must be a number and greater than 0'); + return; + } + _config2['default'].GOAL_BUFFER_LENGTH = v; + } +}); + +// A fudge factor to apply to advertised playlist bitrates to account for +// temporary flucations in client bandwidth +var BANDWIDTH_VARIANCE = 1.2; + +/** + * Returns the CSS value for the specified property on an element + * using `getComputedStyle`. Firefox has a long-standing issue where + * getComputedStyle() may return null when running in an iframe with + * `display: none`. + * + * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397 + * @param {HTMLElement} el the htmlelement to work on + * @param {string} the proprety to get the style for + */ +var safeGetComputedStyle = function safeGetComputedStyle(el, property) { + var result = undefined; + + if (!el) { + return ''; + } + + result = getComputedStyle(el); + if (!result) { + return ''; + } + + return result[property]; +}; + +/** + * Chooses the appropriate media playlist based on the current + * bandwidth estimate and the player size. + * + * @return {Playlist} the highest bitrate playlist less than the currently detected + * bandwidth, accounting for some amount of bandwidth variance + */ +Hls.STANDARD_PLAYLIST_SELECTOR = function () { + var effectiveBitrate = undefined; + var sortedPlaylists = this.playlists.master.playlists.slice(); + var bandwidthPlaylists = []; + var now = +new Date(); + var i = undefined; + var variant = undefined; + var bandwidthBestVariant = undefined; + var resolutionPlusOne = undefined; + var resolutionPlusOneAttribute = undefined; + var resolutionBestVariant = undefined; + var width = undefined; + var height = undefined; + + sortedPlaylists.sort(Hls.comparePlaylistBandwidth); + + // filter out any playlists that have been excluded due to + // incompatible configurations or playback errors + sortedPlaylists = sortedPlaylists.filter(function (localVariant) { + if (typeof localVariant.excludeUntil !== 'undefined') { + return now >= localVariant.excludeUntil; + } + return true; + }); + + // filter out any variant that has greater effective bitrate + // than the current estimated bandwidth + i = sortedPlaylists.length; + while (i--) { + variant = sortedPlaylists[i]; + + // ignore playlists without bandwidth information + if (!variant.attributes || !variant.attributes.BANDWIDTH) { + continue; + } + + effectiveBitrate = variant.attributes.BANDWIDTH * BANDWIDTH_VARIANCE; + + if (effectiveBitrate < this.bandwidth) { + bandwidthPlaylists.push(variant); + + // since the playlists are sorted in ascending order by + // bandwidth, the first viable variant is the best + if (!bandwidthBestVariant) { + bandwidthBestVariant = variant; + } + } + } + + i = bandwidthPlaylists.length; + + // sort variants by resolution + bandwidthPlaylists.sort(Hls.comparePlaylistResolution); + + // forget our old variant from above, + // or we might choose that in high-bandwidth scenarios + // (this could be the lowest bitrate rendition as we go through all of them above) + variant = null; + + width = parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10); + height = parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10); + + // iterate through the bandwidth-filtered playlists and find + // best rendition by player dimension + while (i--) { + variant = bandwidthPlaylists[i]; + + // ignore playlists without resolution information + if (!variant.attributes || !variant.attributes.RESOLUTION || !variant.attributes.RESOLUTION.width || !variant.attributes.RESOLUTION.height) { + continue; + } + + // since the playlists are sorted, the first variant that has + // dimensions less than or equal to the player size is the best + var variantResolution = variant.attributes.RESOLUTION; + + if (variantResolution.width === width && variantResolution.height === height) { + // if we have the exact resolution as the player use it + resolutionPlusOne = null; + resolutionBestVariant = variant; + break; + } else if (variantResolution.width < width && variantResolution.height < height) { + // if both dimensions are less than the player use the + // previous (next-largest) variant + break; + } else if (!resolutionPlusOne || variantResolution.width < resolutionPlusOneAttribute.width && variantResolution.height < resolutionPlusOneAttribute.height) { + // If we still haven't found a good match keep a + // reference to the previous variant for the next loop + // iteration + + // By only saving variants if they are smaller than the + // previously saved variant, we ensure that we also pick + // the highest bandwidth variant that is just-larger-than + // the video player + resolutionPlusOne = variant; + resolutionPlusOneAttribute = resolutionPlusOne.attributes.RESOLUTION; + } + } + + // fallback chain of variants + return resolutionPlusOne || resolutionBestVariant || bandwidthBestVariant || sortedPlaylists[0]; +}; + +// HLS is a source handler, not a tech. Make sure attempts to use it +// as one do not cause exceptions. +Hls.canPlaySource = function () { + return _videoJs2['default'].log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.'); +}; + +/** + * Whether the browser has built-in HLS support. + */ +Hls.supportsNativeHls = (function () { + var video = _globalDocument2['default'].createElement('video'); + + // native HLS is definitely not supported if HTML5 video isn't + if (!_videoJs2['default'].getComponent('Html5').isSupported()) { + return false; + } + + // HLS manifests can go by many mime-types + var canPlay = [ + // Apple santioned + 'application/vnd.apple.mpegurl', + // Apple sanctioned for backwards compatibility + 'audio/mpegurl', + // Very common + 'audio/x-mpegurl', + // Very common + 'application/x-mpegurl', + // Included for completeness + 'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl']; + + return canPlay.some(function (canItPlay) { + return (/maybe|probably/i.test(video.canPlayType(canItPlay)) + ); + }); +})(); + +/** + * HLS is a source handler, not a tech. Make sure attempts to use it + * as one do not cause exceptions. + */ +Hls.isSupported = function () { + return _videoJs2['default'].log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.'); +}; + +var Component = _videoJs2['default'].getComponent('Component'); + +/** + * The Hls Handler object, where we orchestrate all of the parts + * of HLS to interact with video.js + * + * @class HlsHandler + * @extends videojs.Component + * @param {Object} source the soruce object + * @param {Tech} tech the parent tech object + * @param {Object} options optional and required options + */ + +var HlsHandler = (function (_Component) { + _inherits(HlsHandler, _Component); + + function HlsHandler(source, tech, options) { + var _this = this; + + _classCallCheck(this, HlsHandler); + + _get(Object.getPrototypeOf(HlsHandler.prototype), 'constructor', this).call(this, tech); + + // tech.player() is deprecated but setup a reference to HLS for + // backwards-compatibility + if (tech.options_ && tech.options_.playerId) { + var _player = (0, _videoJs2['default'])(tech.options_.playerId); + + if (!_player.hasOwnProperty('hls')) { + Object.defineProperty(_player, 'hls', { + get: function get() { + _videoJs2['default'].log.warn('player.hls is deprecated. Use player.tech.hls instead.'); + return _this; + } + }); + } + } + + this.tech_ = tech; + this.source_ = source; + this.stats = {}; + + // handle global & Source Handler level options + this.options_ = _videoJs2['default'].mergeOptions(_videoJs2['default'].options.hls || {}, options.hls); + this.setOptions_(); + + // listen for fullscreenchange events for this player so that we + // can adjust our quality selection quickly + this.on(_globalDocument2['default'], ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], function (event) { + var fullscreenElement = _globalDocument2['default'].fullscreenElement || _globalDocument2['default'].webkitFullscreenElement || _globalDocument2['default'].mozFullScreenElement || _globalDocument2['default'].msFullscreenElement; + + if (fullscreenElement && fullscreenElement.contains(_this.tech_.el())) { + _this.masterPlaylistController_.fastQualityChange_(); + } + }); + + this.on(this.tech_, 'seeking', function () { + this.setCurrentTime(this.tech_.currentTime()); + }); + this.on(this.tech_, 'error', function () { + if (this.masterPlaylistController_) { + this.masterPlaylistController_.pauseLoading(); + } + }); + + this.audioTrackChange_ = function () { + _this.masterPlaylistController_.useAudio(); + }; + + this.on(this.tech_, 'play', this.play); + } + + /** + * The Source Handler object, which informs video.js what additional + * MIME types are supported and sets up playback. It is registered + * automatically to the appropriate tech based on the capabilities of + * the browser it is running in. It is not necessary to use or modify + * this object in normal usage. + */ + + _createClass(HlsHandler, [{ + key: 'setOptions_', + value: function setOptions_() { + var _this2 = this; + + // defaults + this.options_.withCredentials = this.options_.withCredentials || false; + + // start playlist selection at a reasonable bandwidth for + // broadband internet + // 0.5 MB/s + this.options_.bandwidth = this.options_.bandwidth || 4194304; + + // grab options passed to player.src + ['withCredentials', 'bandwidth'].forEach(function (option) { + if (typeof _this2.source_[option] !== 'undefined') { + _this2.options_[option] = _this2.source_[option]; + } + }); + + this.bandwidth = this.options_.bandwidth; + } + + /** + * called when player.src gets called, handle a new source + * + * @param {Object} src the source object to handle + */ + }, { + key: 'src', + value: function src(_src) { + var _this3 = this; + + // do nothing if the src is falsey + if (!_src) { + return; + } + this.setOptions_(); + // add master playlist controller options + this.options_.url = this.source_.src; + this.options_.tech = this.tech_; + this.options_.externHls = Hls; + this.masterPlaylistController_ = new _masterPlaylistController2['default'](this.options_); + + // `this` in selectPlaylist should be the HlsHandler for backwards + // compatibility with < v2 + this.masterPlaylistController_.selectPlaylist = Hls.STANDARD_PLAYLIST_SELECTOR.bind(this); + + // re-expose some internal objects for backwards compatibility with < v2 + this.playlists = this.masterPlaylistController_.masterPlaylistLoader_; + this.mediaSource = this.masterPlaylistController_.mediaSource; + + // Proxy assignment of some properties to the master playlist + // controller. Using a custom property for backwards compatibility + // with < v2 + Object.defineProperties(this, { + selectPlaylist: { + get: function get() { + return this.masterPlaylistController_.selectPlaylist; + }, + set: function set(selectPlaylist) { + this.masterPlaylistController_.selectPlaylist = selectPlaylist.bind(this); + } + }, + bandwidth: { + get: function get() { + return this.masterPlaylistController_.mainSegmentLoader_.bandwidth; + }, + set: function set(bandwidth) { + this.masterPlaylistController_.mainSegmentLoader_.bandwidth = bandwidth; + } + } + }); + + Object.defineProperty(this.stats, 'bandwidth', { + get: function get() { + return _this3.bandwidth || 0; + } + }); + Object.defineProperty(this.stats, 'mediaRequests', { + get: function get() { + return _this3.masterPlaylistController_.mediaRequests_() || 0; + } + }); + Object.defineProperty(this.stats, 'mediaTransferDuration', { + get: function get() { + return _this3.masterPlaylistController_.mediaTransferDuration_() || 0; + } + }); + Object.defineProperty(this.stats, 'mediaBytesTransferred', { + get: function get() { + return _this3.masterPlaylistController_.mediaBytesTransferred_() || 0; + } + }); + + this.tech_.one('canplay', this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_)); + + this.masterPlaylistController_.on('sourceopen', function () { + _this3.tech_.audioTracks().addEventListener('change', _this3.audioTrackChange_); + }); + + this.masterPlaylistController_.on('audioinfo', function (e) { + if (!_videoJs2['default'].browser.IS_FIREFOX || !_this3.audioInfo_ || !objectChanged(_this3.audioInfo_, e.info)) { + _this3.audioInfo_ = e.info; + return; + } + + var error = 'had different audio properties (channels, sample rate, etc.) ' + 'or changed in some other way. This behavior is currently ' + 'unsupported in Firefox due to an issue: \n\n' + 'https://bugzilla.mozilla.org/show_bug.cgi?id=1247138\n\n'; + + var enabledTrack = undefined; + var defaultTrack = undefined; + + _this3.masterPlaylistController_.audioTracks_.forEach(function (t) { + if (!defaultTrack && t['default']) { + defaultTrack = t; + } + + if (!enabledTrack && t.enabled) { + enabledTrack = t; + } + }); + + // they did not switch audiotracks + // blacklist the current playlist + if (!enabledTrack.getLoader(_this3.activeAudioGroup_())) { + error = 'The rendition that we tried to switch to ' + error + 'Unfortunately that means we will have to blacklist ' + 'the current playlist and switch to another. Sorry!'; + _this3.masterPlaylistController_.blacklistCurrentPlaylist(); + } else { + error = 'The audio track \'' + enabledTrack.label + '\' that we tried to ' + ('switch to ' + error + ' Unfortunately this means we will have to ') + ('return you to the main track \'' + defaultTrack.label + '\'. Sorry!'); + defaultTrack.enabled = true; + _this3.tech_.audioTracks().removeTrack(enabledTrack); + } + + _videoJs2['default'].log.warn(error); + _this3.masterPlaylistController_.useAudio(); + }); + this.masterPlaylistController_.on('selectedinitialmedia', function () { + // clear current audioTracks + _this3.tech_.clearTracks('audio'); + _this3.masterPlaylistController_.audioTracks_.forEach(function (track) { + _this3.tech_.audioTracks().addTrack(track); + }); + + // Add the manual rendition mix-in to HlsHandler + (0, _renditionMixin2['default'])(_this3); + }); + + // the bandwidth of the primary segment loader is our best + // estimate of overall bandwidth + this.on(this.masterPlaylistController_, 'progress', function () { + this.bandwidth = this.masterPlaylistController_.mainSegmentLoader_.bandwidth; + this.tech_.trigger('progress'); + }); + + // do nothing if the tech has been disposed already + // this can occur if someone sets the src in player.ready(), for instance + if (!this.tech_.el()) { + return; + } + + this.tech_.src(_videoJs2['default'].URL.createObjectURL(this.masterPlaylistController_.mediaSource)); + } + + /** + * a helper for grabbing the active audio group from MasterPlaylistController + * + * @private + */ + }, { + key: 'activeAudioGroup_', + value: function activeAudioGroup_() { + return this.masterPlaylistController_.activeAudioGroup(); + } + + /** + * Begin playing the video. + */ + }, { + key: 'play', + value: function play() { + this.masterPlaylistController_.play(); + } + + /** + * a wrapper around the function in MasterPlaylistController + */ + }, { + key: 'setCurrentTime', + value: function setCurrentTime(currentTime) { + this.masterPlaylistController_.setCurrentTime(currentTime); + } + + /** + * a wrapper around the function in MasterPlaylistController + */ + }, { + key: 'duration', + value: function duration() { + return this.masterPlaylistController_.duration(); + } + + /** + * a wrapper around the function in MasterPlaylistController + */ + }, { + key: 'seekable', + value: function seekable() { + return this.masterPlaylistController_.seekable(); + } + + /** + * Abort all outstanding work and cleanup. + */ + }, { + key: 'dispose', + value: function dispose() { + if (this.masterPlaylistController_) { + this.masterPlaylistController_.dispose(); + } + this.tech_.audioTracks().removeEventListener('change', this.audioTrackChange_); + _get(Object.getPrototypeOf(HlsHandler.prototype), 'dispose', this).call(this); + } + }]); + + return HlsHandler; +})(Component); + +var HlsSourceHandler = function HlsSourceHandler(mode) { + return { + canHandleSource: function canHandleSource(srcObj) { + // this forces video.js to skip this tech/mode if its not the one we have been + // overriden to use, by returing that we cannot handle the source. + if (_videoJs2['default'].options.hls && _videoJs2['default'].options.hls.mode && _videoJs2['default'].options.hls.mode !== mode) { + return false; + } + return HlsSourceHandler.canPlayType(srcObj.type); + }, + handleSource: function handleSource(source, tech, options) { + if (mode === 'flash') { + // We need to trigger this asynchronously to give others the chance + // to bind to the event when a source is set at player creation + tech.setTimeout(function () { + tech.trigger('loadstart'); + }, 1); + } + + var settings = _videoJs2['default'].mergeOptions(options, { hls: { mode: mode } }); + + tech.hls = new HlsHandler(source, tech, settings); + + tech.hls.xhr = (0, _xhr2['default'])(); + // Use a global `before` function if specified on videojs.Hls.xhr + // but still allow for a per-player override + if (_videoJs2['default'].Hls.xhr.beforeRequest) { + tech.hls.xhr.beforeRequest = _videoJs2['default'].Hls.xhr.beforeRequest; + } + + tech.hls.src(source.src); + return tech.hls; + }, + canPlayType: function canPlayType(type) { + if (HlsSourceHandler.canPlayType(type)) { + return 'maybe'; + } + return ''; + } + }; +}; + +/** + * A comparator function to sort two playlist object by bandwidth. + * + * @param {Object} left a media playlist object + * @param {Object} right a media playlist object + * @return {Number} Greater than zero if the bandwidth attribute of + * left is greater than the corresponding attribute of right. Less + * than zero if the bandwidth of right is greater than left and + * exactly zero if the two are equal. + */ +Hls.comparePlaylistBandwidth = function (left, right) { + var leftBandwidth = undefined; + var rightBandwidth = undefined; + + if (left.attributes && left.attributes.BANDWIDTH) { + leftBandwidth = left.attributes.BANDWIDTH; + } + leftBandwidth = leftBandwidth || window.Number.MAX_VALUE; + if (right.attributes && right.attributes.BANDWIDTH) { + rightBandwidth = right.attributes.BANDWIDTH; + } + rightBandwidth = rightBandwidth || window.Number.MAX_VALUE; + + return leftBandwidth - rightBandwidth; +}; + +/** + * A comparator function to sort two playlist object by resolution (width). + * @param {Object} left a media playlist object + * @param {Object} right a media playlist object + * @return {Number} Greater than zero if the resolution.width attribute of + * left is greater than the corresponding attribute of right. Less + * than zero if the resolution.width of right is greater than left and + * exactly zero if the two are equal. + */ +Hls.comparePlaylistResolution = function (left, right) { + var leftWidth = undefined; + var rightWidth = undefined; + + if (left.attributes && left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) { + leftWidth = left.attributes.RESOLUTION.width; + } + + leftWidth = leftWidth || window.Number.MAX_VALUE; + + if (right.attributes && right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) { + rightWidth = right.attributes.RESOLUTION.width; + } + + rightWidth = rightWidth || window.Number.MAX_VALUE; + + // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions + // have the same media dimensions/ resolution + if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) { + return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH; + } + return leftWidth - rightWidth; +}; + +HlsSourceHandler.canPlayType = function (type) { + var mpegurlRE = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i; + + // favor native HLS support if it's available + if (Hls.supportsNativeHls) { + return false; + } + return mpegurlRE.test(type); +}; + +if (typeof _videoJs2['default'].MediaSource === 'undefined' || typeof _videoJs2['default'].URL === 'undefined') { + _videoJs2['default'].MediaSource = _videojsContribMediaSources.MediaSource; + _videoJs2['default'].URL = _videojsContribMediaSources.URL; +} + +// register source handlers with the appropriate techs +if (_videojsContribMediaSources.MediaSource.supportsNativeMediaSources()) { + _videoJs2['default'].getComponent('Html5').registerSourceHandler(HlsSourceHandler('html5')); +} +if (window.Uint8Array) { + _videoJs2['default'].getComponent('Flash').registerSourceHandler(HlsSourceHandler('flash')); +} + +_videoJs2['default'].HlsHandler = HlsHandler; +_videoJs2['default'].HlsSourceHandler = HlsSourceHandler; +_videoJs2['default'].Hls = Hls; +_videoJs2['default'].m3u8 = _m3u8Parser2['default']; +_videoJs2['default'].registerComponent('Hls', Hls); +_videoJs2['default'].options.hls = _videoJs2['default'].options.hls || {}; + +module.exports = { + Hls: Hls, + HlsHandler: HlsHandler, + HlsSourceHandler: HlsSourceHandler +}; +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./bin-utils":1,"./config":2,"./decrypter":6,"./master-playlist-controller":8,"./playlist":10,"./playlist-loader":9,"./rendition-mixin":12,"./xhr":17,"global/document":19,"m3u8-parser":56,"videojs-contrib-media-sources":73}]},{},[91])(91) +}); diff --git a/vendor/assets/javascripts/videojs-resolution-switcher.js b/vendor/assets/javascripts/videojs-resolution-switcher.js new file mode 100644 index 0000000..41d751c --- /dev/null +++ b/vendor/assets/javascripts/videojs-resolution-switcher.js @@ -0,0 +1,361 @@ +/*! videojs-resolution-switcher - 2015-7-26 + * Copyright (c) 2016 Kasper Moskwiak + * Modified by Pierre Kraft + * Licensed under the Apache-2.0 license. */ + +(function() { + /* jshint eqnull: true*/ + /* global require */ + 'use strict'; + var videojs = null; + if(typeof window.videojs === 'undefined' && typeof require === 'function') { + videojs = require('video.js'); + } else { + videojs = window.videojs; + } + + (function(window, videojs) { + + + var defaults = {}, + videoJsResolutionSwitcher, + currentResolution = {}, // stores current resolution + menuItemsHolder = {}; // stores menuItems + + function setSourcesSanitized(player, sources, label, customSourcePicker) { + currentResolution = { + label: label, + sources: sources + }; + if(typeof customSourcePicker === 'function'){ + return customSourcePicker(player, sources, label); + } + return player.src(sources.map(function(src) { + return {src: src.src, type: src.type, res: src.res}; + })); + } + + /* + * Resolution menu item + */ + var MenuItem = videojs.getComponent('MenuItem'); + var ResolutionMenuItem = videojs.extend(MenuItem, { + constructor: function(player, options, onClickListener, label){ + this.onClickListener = onClickListener; + this.label = label; + // Sets this.player_, this.options_ and initializes the component + MenuItem.call(this, player, options); + this.src = options.src; + + this.on('click', this.onClick); + this.on('touchstart', this.onClick); + + if (options.initialySelected) { + this.showAsLabel(); + this.selected(true); + + this.addClass('vjs-selected'); + } + }, + showAsLabel: function() { + // Change menu button label to the label of this item if the menu button label is provided + if(this.label) { + this.label.innerHTML = this.options_.label; + } + }, + onClick: function(customSourcePicker){ + this.onClickListener(this); + // Remember player state + var currentTime = this.player_.currentTime(); + var isPaused = this.player_.paused(); + this.showAsLabel(); + + // add .current class + this.addClass('vjs-selected'); + + // Hide bigPlayButton + if(!isPaused){ + this.player_.bigPlayButton.hide(); + } + if(typeof customSourcePicker !== 'function' && + typeof this.options_.customSourcePicker === 'function'){ + customSourcePicker = this.options_.customSourcePicker; + } + // Change player source and wait for loadeddata event, then play video + // loadedmetadata doesn't work right now for flash. + // Probably because of https://github.com/videojs/video-js-swf/issues/124 + // If player preload is 'none' and then loadeddata not fired. So, we need timeupdate event for seek handle (timeupdate doesn't work properly with flash) + var handleSeekEvent = 'loadeddata'; + if(this.player_.techName_ !== 'Youtube' && this.player_.preload() === 'none' && this.player_.techName_ !== 'Flash') { + handleSeekEvent = 'timeupdate'; + } + setSourcesSanitized(this.player_, this.src, this.options_.label, customSourcePicker).one(handleSeekEvent, function() { + this.player_.currentTime(currentTime); + this.player_.handleTechSeeked_(); + if(!isPaused){ + // Start playing and hide loadingSpinner (flash issue ?) + this.player_.play().handleTechSeeked_(); + } + this.player_.trigger('resolutionchange'); + }); + } + }); + + + /* + * Resolution menu button + */ + var MenuButton = videojs.getComponent('MenuButton'); + var ResolutionMenuButton = videojs.extend(MenuButton, { + constructor: function(player, options, settings, label){ + this.sources = options.sources; + this.label = label; + this.label.innerHTML = options.initialySelectedLabel; + // Sets this.player_, this.options_ and initializes the component + MenuButton.call(this, player, options, settings); + this.controlText('Quality'); + + if(settings.dynamicLabel){ + this.el().appendChild(label); + }else{ + var staticLabel = document.createElement('span'); + videojs.addClass(staticLabel, 'vjs-resolution-button-staticlabel'); + this.el().appendChild(staticLabel); + } + }, + createItems: function(){ + var menuItems = []; + var labels = (this.sources && this.sources.label) || {}; + var onClickUnselectOthers = function(clickedItem) { + menuItems.map(function(item) { + item.selected(item === clickedItem); + item.removeClass('vjs-selected'); + }); + }; + + for (var key in labels) { + if (labels.hasOwnProperty(key)) { + menuItems.push(new ResolutionMenuItem( + this.player_, + { + label: key, + src: labels[key], + initialySelected: key === this.options_.initialySelectedLabel, + customSourcePicker: this.options_.customSourcePicker + }, + onClickUnselectOthers, + this.label)); + // Store menu item for API calls + menuItemsHolder[key] = menuItems[menuItems.length - 1]; + } + } + return menuItems; + } + }); + + /** + * Initialize the plugin. + * @param {object} [options] configuration for the plugin + */ + videoJsResolutionSwitcher = function(options) { + var settings = videojs.mergeOptions(defaults, options), + player = this, + label = document.createElement('span'), + groupedSrc = {}; + + videojs.addClass(label, 'vjs-resolution-button-label'); + + /** + * Updates player sources or returns current source URL + * @param {Array} [src] array of sources [{src: '', type: '', label: '', res: ''}] + * @returns {Object|String|Array} videojs player object if used as setter or current source URL, object, or array of sources + */ + player.updateSrc = function(src){ + //Return current src if src is not given + if(!src){ return player.src(); } + // Dispose old resolution menu button before adding new sources + if(player.controlBar.resolutionSwitcher){ + player.controlBar.resolutionSwitcher.dispose(); + delete player.controlBar.resolutionSwitcher; + } + //Sort sources + src = src.sort(compareResolutions); + groupedSrc = bucketSources(src); + var choosen = chooseSrc(groupedSrc, src); + var menuButton = new ResolutionMenuButton(player, { sources: groupedSrc, initialySelectedLabel: choosen.label , initialySelectedRes: choosen.res , customSourcePicker: settings.customSourcePicker}, settings, label); + videojs.addClass(menuButton.el(), 'vjs-resolution-button'); + player.controlBar.resolutionSwitcher = player.controlBar.el_.insertBefore(menuButton.el_, player.controlBar.getChild('fullscreenToggle').el_); + player.controlBar.resolutionSwitcher.dispose = function(){ + this.parentNode.removeChild(this); + }; + return setSourcesSanitized(player, choosen.sources, choosen.label); + }; + + /** + * Returns current resolution or sets one when label is specified + * @param {String} [label] label name + * @param {Function} [customSourcePicker] custom function to choose source. Takes 3 arguments: player, sources, label. Must return player object. + * @returns {Object} current resolution object {label: '', sources: []} if used as getter or player object if used as setter + */ + player.currentResolution = function(label, customSourcePicker){ + if(label == null) { return currentResolution; } + if(menuItemsHolder[label] != null){ + menuItemsHolder[label].onClick(customSourcePicker); + } + return player; + }; + + /** + * Returns grouped sources by label, resolution and type + * @returns {Object} grouped sources: { label: { key: [] }, res: { key: [] }, type: { key: [] } } + */ + player.getGroupedSrc = function(){ + return groupedSrc; + }; + + /** + * Method used for sorting list of sources + * @param {Object} a - source object with res property + * @param {Object} b - source object with res property + * @returns {Number} result of comparation + */ + function compareResolutions(a, b){ + if(!a.res || !b.res){ return 0; } + return (+b.res)-(+a.res); + } + + /** + * Group sources by label, resolution and type + * @param {Array} src Array of sources + * @returns {Object} grouped sources: { label: { key: [] }, res: { key: [] }, type: { key: [] } } + */ + function bucketSources(src){ + var resolutions = { + label: {}, + res: {}, + type: {} + }; + src.map(function(source) { + initResolutionKey(resolutions, 'label', source); + initResolutionKey(resolutions, 'res', source); + initResolutionKey(resolutions, 'type', source); + + appendSourceToKey(resolutions, 'label', source); + appendSourceToKey(resolutions, 'res', source); + appendSourceToKey(resolutions, 'type', source); + }); + return resolutions; + } + + function initResolutionKey(resolutions, key, source) { + if(resolutions[key][source[key]] == null) { + resolutions[key][source[key]] = []; + } + } + + function appendSourceToKey(resolutions, key, source) { + resolutions[key][source[key]].push(source); + } + + /** + * Choose src if option.default is specified + * @param {Object} groupedSrc {res: { key: [] }} + * @param {Array} src Array of sources sorted by resolution used to find high and low res + * @returns {Object} {res: string, sources: []} + */ + function chooseSrc(groupedSrc, src){ + var selectedRes = settings['default']; // use array access as default is a reserved keyword + var selectedLabel = ''; + if (selectedRes === 'high') { + selectedRes = src[0].res; + selectedLabel = src[0].label; + } else if (selectedRes === 'low' || selectedRes == null) { + // Select low-res if default is low or not set + selectedRes = src[src.length - 1].res; + selectedLabel = src[src.length -1].label; + } else if (groupedSrc.res[selectedRes]) { + selectedLabel = groupedSrc.res[selectedRes][0].label; + } + + if(selectedRes === undefined){ + return {res: selectedRes, label: selectedLabel, sources: groupedSrc.label[selectedLabel]}; + } + return {res: selectedRes, label: selectedLabel, sources: groupedSrc.res[selectedRes]}; + } + + function initResolutionForYt(player){ + // Init resolution + player.tech_.ytPlayer.setPlaybackQuality('default'); + + // Capture events + player.tech_.ytPlayer.addEventListener('onPlaybackQualityChange', function(){ + player.trigger('resolutionchange'); + }); + + // We must wait for play event + player.one('play', function(){ + var qualities = player.tech_.ytPlayer.getAvailableQualityLevels(); + // Map youtube qualities names + var _yts = { + highres: {res: 1080, label: '1080', yt: 'highres'}, + hd1080: {res: 1080, label: '1080', yt: 'hd1080'}, + hd720: {res: 720, label: '720', yt: 'hd720'}, + large: {res: 480, label: '480', yt: 'large'}, + medium: {res: 360, label: '360', yt: 'medium'}, + small: {res: 240, label: '240', yt: 'small'}, + tiny: {res: 144, label: '144', yt: 'tiny'}, + auto: {res: 0, label: 'auto', yt: 'default'} + }; + + var _sources = []; + + qualities.map(function(q){ + _sources.push({ + src: player.src().src, + type: player.src().type, + label: _yts[q].label, + res: _yts[q].res, + _yt: _yts[q].yt + }); + }); + + groupedSrc = bucketSources(_sources); + + // Overwrite defualt sourcePicer function + var _customSourcePicker = function(_player, _sources, _label){ + player.tech_.ytPlayer.setPlaybackQuality(_sources[0]._yt); + return player; + }; + + var choosen = {label: 'auto', res: 0, sources: groupedSrc.label.auto}; + var menuButton = new ResolutionMenuButton(player, { + sources: groupedSrc, + initialySelectedLabel: choosen.label, + initialySelectedRes: choosen.res, + customSourcePicker: _customSourcePicker + }, settings, label); + + menuButton.el().classList.add('vjs-resolution-button'); + player.controlBar.resolutionSwitcher = player.controlBar.addChild(menuButton); + }); + } + + player.ready(function(){ + if(player.options_.sources.length > 1){ + // tech: Html5 and Flash + // Create resolution switcher for videos form tag inside