From 92cb33ec728314e49510fe989af885bfd2852541 Mon Sep 17 00:00:00 2001 From: =?utf8?q?V=C3=A1s=C3=A1ry=20D=C3=A1niel?= Date: Thu, 8 Mar 2018 11:41:32 +0000 Subject: [PATCH] git-tfs-id: [http://tfs.userrendszerhaz.hu:8080/tfs/DefaultCollection]$/MediaCube;C30959 --- .../server/steps/ImportStatisticsStep.java | 10 + .../src/user/jobengine/db/IItemManager.java | 2 + .../src/user/jobengine/db/ItemManager.java | 49 +- .../src/user/jobengine/db/Media.java | 10 +- .../css/6.1.0-video-js.css | 1393 + .../css/video-js.css | 95 +- .../js/6.1.0-video.js | 27410 ++++++++++ .../js/6.1.0-videojs-ie8.min.js | 1 + server/user.jobengine.osgi.server/js/video.js | 42139 ++++++++-------- .../pages/mediaplayer.jsp | 72 +- .../pages/search_items.zul | 40 +- .../pages/statistics.zul | 15 +- .../jobengine/zk/model/CachedListModel.java | 4 +- .../user/jobengine/zk/model/SearchModel.java | 10 +- .../zk/model/StatisticsComposer.java | 74 +- 15 files changed, 49450 insertions(+), 21874 deletions(-) create mode 100644 server/user.jobengine.osgi.server/css/6.1.0-video-js.css create mode 100644 server/user.jobengine.osgi.server/js/6.1.0-video.js create mode 100644 server/user.jobengine.osgi.server/js/6.1.0-videojs-ie8.min.js diff --git a/server/user.jobengine.executors/src/user/jobengine/server/steps/ImportStatisticsStep.java b/server/user.jobengine.executors/src/user/jobengine/server/steps/ImportStatisticsStep.java index 5a73d619..63e3f7e8 100644 --- a/server/user.jobengine.executors/src/user/jobengine/server/steps/ImportStatisticsStep.java +++ b/server/user.jobengine.executors/src/user/jobengine/server/steps/ImportStatisticsStep.java @@ -130,6 +130,16 @@ public class ImportStatisticsStep extends JobStep { raw.put("ingest_duration", ingestDuration); } + long archiveCount = 0; + long archiveDuration = 0; + BasicDBObject archiveInfo = manager.getArchiveInfo(scheduledDate, String.valueOf(parentStoryId)); + if (archiveInfo != null) { + archiveCount = ingestInfo.getLong("count"); + archiveDuration = ingestInfo.getLong("duration"); + raw.put("archive_count", archiveCount); + raw.put("archive_duration", archiveDuration); + } + rawData.add(raw); //planStat diff --git a/server/user.jobengine.osgi.db/src/user/jobengine/db/IItemManager.java b/server/user.jobengine.osgi.db/src/user/jobengine/db/IItemManager.java index 4278d8a6..10110dbb 100644 --- a/server/user.jobengine.osgi.db/src/user/jobengine/db/IItemManager.java +++ b/server/user.jobengine.osgi.db/src/user/jobengine/db/IItemManager.java @@ -122,6 +122,8 @@ public interface IItemManager extends IEntityPersister { */ List getAllCached(Class baseClass); + BasicDBObject getArchiveInfo(Calendar scheduleDate, String houseId); + /** * Visszaadja az entitásnak megfelelő DAO objektumot. * diff --git a/server/user.jobengine.osgi.db/src/user/jobengine/db/ItemManager.java b/server/user.jobengine.osgi.db/src/user/jobengine/db/ItemManager.java index cc67e642..7eb74c9f 100644 --- a/server/user.jobengine.osgi.db/src/user/jobengine/db/ItemManager.java +++ b/server/user.jobengine.osgi.db/src/user/jobengine/db/ItemManager.java @@ -437,6 +437,51 @@ public class ItemManager extends MemoryCache implements IItemManager { return result; } + @Override + public BasicDBObject getArchiveInfo(Calendar scheduleDate, String houseId) { + BasicDBObject result = null; + ResultSet rs = null; + PreparedStatement st = null; + DefaultContext context = getDbContext(); + Connection connection = context.getConnection(); + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd"); + try { + String date = df.format(scheduleDate.getTime()); + String query = String.format( + "SELECT COUNT(*) as count, SUM(length) as duration FROM VW_ITEMS WHERE houseid='%s' AND archived LIKE '%s%%' GROUP BY houseid", houseId, + date); + st = connection.prepareStatement(query); + rs = st.executeQuery(); + if (rs.next()) { + result = new BasicDBObject(); + result.put("count", rs.getLong("count")); + result.put("duration", rs.getLong("duration")); + } + + connection.commit(); + } catch (Exception e) { + try { + connection.rollback(); + } catch (Exception e1) { + } + throwError(e); + } finally { + try { + if (rs != null) + rs.close(); + } catch (Exception e1) { + } + try { + if (st != null) + st.close(); + } catch (Exception e1) { + } + putDbContext(context); + } + + return result; + } + @Override public IEntityBaseDAO getBaseDAO(Class classInfo) { IEntityBaseDAO entityBaseDb = null; @@ -516,7 +561,7 @@ public class ItemManager extends MemoryCache implements IItemManager { } @Override - public BasicDBObject getIngestInfo(Calendar scheduleDate, String houseid) { + public BasicDBObject getIngestInfo(Calendar scheduleDate, String houseId) { BasicDBObject result = null; ResultSet rs = null; PreparedStatement st = null; @@ -526,7 +571,7 @@ public class ItemManager extends MemoryCache implements IItemManager { try { String date = df.format(scheduleDate.getTime()); String query = String.format( - "SELECT COUNT(*) as count, SUM(size) as duration FROM VW_RD_INGEST WHERE HOUSEID='%s' AND FINISHED LIKE '%s%%' GROUP BY HOUSEID", houseid, + "SELECT COUNT(*) as count, SUM(size) as duration FROM VW_RD_INGEST WHERE houseid='%s' AND finished LIKE '%s%%' GROUP BY houseid", houseId, date); st = connection.prepareStatement(query); rs = st.executeQuery(); diff --git a/server/user.jobengine.osgi.db/src/user/jobengine/db/Media.java b/server/user.jobengine.osgi.db/src/user/jobengine/db/Media.java index 3c95f49c..696f24ba 100644 --- a/server/user.jobengine.osgi.db/src/user/jobengine/db/Media.java +++ b/server/user.jobengine.osgi.db/src/user/jobengine/db/Media.java @@ -76,7 +76,15 @@ public class Media extends DynamicAttributes { } public String getMediaFilesName() { - return (getMediaFiles() == null || getMediaFiles().size() == 0) ? "" : getMediaFiles().get(0).getHouseId(); + String result = null; + if (getMediaFiles() != null && getMediaFiles().size() > 0) { + for (MediaFile mf : getMediaFiles()) { + result = mf.getHouseId(); + if (result != null) + break; + } + } + return result; } public byte[] getPoster() { diff --git a/server/user.jobengine.osgi.server/css/6.1.0-video-js.css b/server/user.jobengine.osgi.server/css/6.1.0-video-js.css new file mode 100644 index 00000000..33c9f56e --- /dev/null +++ b/server/user.jobengine.osgi.server/css/6.1.0-video-js.css @@ -0,0 +1,1393 @@ +.video-js .vjs-big-play-button .vjs-icon-placeholder:before, .vjs-button > .vjs-icon-placeholder:before, .video-js .vjs-modal-dialog, .vjs-modal-dialog .vjs-modal-dialog-content { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; } + +.video-js .vjs-big-play-button .vjs-icon-placeholder:before, .vjs-button > .vjs-icon-placeholder:before { + text-align: center; } + +@font-face { + font-family: VideoJS; + src: url("../font/2.0.0/VideoJS.eot?#iefix") format("eot"); } + +@font-face { + font-family: VideoJS; + src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAA54AAoAAAAAFmgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAAA9AAAAD4AAABWUZFeBWNtYXAAAAE0AAAAOgAAAUriMBC2Z2x5ZgAAAXAAAAouAAAPUFvx6AdoZWFkAAALoAAAACsAAAA2DIPpX2hoZWEAAAvMAAAAGAAAACQOogcgaG10eAAAC+QAAAAPAAAAfNkAAABsb2NhAAAL9AAAAEAAAABAMMg06m1heHAAAAw0AAAAHwAAACABMAB5bmFtZQAADFQAAAElAAACCtXH9aBwb3N0AAANfAAAAPwAAAGBZkSN43icY2BkZ2CcwMDKwMFSyPKMgYHhF4RmjmEIZzzHwMDEwMrMgBUEpLmmMDh8ZPwoxw7iLmSHCDOCCADvEAo+AAB4nGNgYGBmgGAZBkYGEHAB8hjBfBYGDSDNBqQZGZgYGD7K/f8PUvCREUTzM0DVAwEjG8OIBwCPdwbVAAB4nI1Xe1CU1xX/zv1eLItLln0JwrIfC7sJGET2hRJ2N1GUoBJE8AESQEEhmBHjaB7UuBMTO4GMaSu7aY3RNlOdRPNqO2pqRmuTaSZtR6JJILUZk00a/4imjpmiecB303O/XUgMJOPufvd+99xzzz33nN855y4HHH7EfrGfIxwHRiANvF/sH71I9BzHszmpW+rGOQOXxXE6YhI4PoMT8zkT4cDFuf1cwMrZJI5cglM0HKVv0MaUFDgIFfg9mJJCG+kbKn1JkqBOVaFOkuhLpARq8fu0Nnc9/zdvfY9PxXW4PdH0C6N+PCejhorxFjAqRjgFRXSINEARbBGsoxcFK7IJmr4OycFJnInL59zIXwxui80fkGRbEHyosMWaATJKUfCskmwJQsAWANkmnIGOhlf514h7U8HNIv3owoHB0WMt0Eb3sx0guLi5pq/8Ny1q6969fKR9X9GBV6dPv6dp04K99SOwtmyPl47ApRa6n4ZpP1yjr5fn7MmYP/vXLUJs715UguklHBaHOZHZmG1N9FAIW2mf0MqWCIdo/8RZ1yGfxKUldDcGIbFA7ICO+vqOMSPTh/ZrSqgHi/bB/O8E8Mnzp+M+acxfpsTShBwej26TiGxBn7m4eEIO+Rueu6Hj+IFBnh88cAEUEQ//nVLx5C7kf+yIR47QEe+eMlhz9SqsGbe3hh2R03NGzoY6O42Kz8l7fB6fAk6LYnTyFo/FYyT6GGyNx2Jx2sdH4rA1Fo/HyCXaFyOp8dhYBCfJb2NIn1ImE6CYNGmgSTb52DawJR6jfXEmDU4xyTEmpgHHOIStoxfjSGdkbsK2w2jbdMQG4sgAstEONgURYCwGHhEhhscioQaAhhCf7McifEQc0l6+mxj9nI+gmSdiQ0Zbm7gZnIO7GSMEXG6UDAVocxAV8GcEXCKg1a02RcTtwANWRGIAyElor6n/+ZU2yOB3+T77Hb1MLqhn4KHVnQBjJnqe9QZSon6Kc5DxAD2vMdPL/BXSmQGwspa67z9wLUjdi9TN7QC7lyyBr9rpt7uXVC1CMpyjKRoXnGPHTuiaPLsNdc2dbAFQLAooPkXEh33FodHl4XpC6sPCIa0ftUIhHSYXVSu5iME+DIXsbZJ51BeidCgajcai43jU9nVzoSn2dPqcFvSoxSzJzgRKAx47WMRxOrIj3Wf0+hndxhJTiOkSEqxar3b3RKM9hY64oxBA64ieURLvCfpkDb8siBdUJ1bgT+urJ5PGfewQrmm5R5+0HmfyIPySD7OYkT0WxRePah8oEiyjlxIP74thVoRTURpmL6QhGuWS+QDjdANXjIM8SQa/1w128ODx0Qp4aLMNg9+JL3joUn8AMxW+aLNiuKjarn4uyyTdXjOzZTsh21uwldUvJoYza+zELALfu3p1L8/3krtyZ0Ag058J3hxHghvbGZn0dHZy6Mim/7Blre4lpHd1c28yVqRViO153F2oIWoXCIKbL4Z0cM1iaQn9mI5KuV2SzEvWXJDMNtkANpMdQoDDhIdD4A/YrP6Aye9ysxyE+uOEAcTDorgvVZJjcua043PnZ/PmdDqcbibZlXOOT8uSo7Kof0YUn9GL+Jo17ficymxiTofC6znUso0DhAxs1Fo+kF+d36vLmgZ8mk5cdGv2mwYj5k3Dm9m3LhJ1aVRNm6HrTbLgYAoWXDhDd/u4PGy5CT+xGMdiaBovewUCF/1BiWNljI9MLn7jeScpg+WyH6mfU62eVDql7hsrmvx1ezp/YldE2LhjbkiDnAn8tGy/MW3IXRMYJduvq9HpmIcKuFt+JCtgdGEGKAcF6UacVwIYbVPGfw/+YuNBS4cx/CUHcnyfc+wRDMtTr72mMSBjT/yn/GKSdeDWQUCH6Xoqq5R10RE60gV6erUL0iCti16d0hZjxut4QI/rEpgSh6WjnJXdBXRg1GKCucGJPtFqM27aD1tOqqKonsQ2KsFSSmEpmvRlsR+TcD9OFwrqXxIclL4sJTnGMSuG8KpkZvKdeVIOKDyWSyPLV16/p1QMPbP8NihwUzr47bdnXtwtjdCvqqpO0H+pOvIl3Pzv46e5CT/tQjklXCXXym1AaWY7bzHLkuDMc7ldKCvgxzLn8wYkJLBhEDyK7MT8bTbwbkxbfp+3mKAGsmTBpabSIEECzMIcQlzOPAMKsxMs7uhsnxPLuofPDTc1hkuq6MX9j16YU7CqegcYHbmWYuvAP6tCS97tgWf7dlQvnl25YPavXLVZvrzQPeHCpZmzzEUVq/xzu5sChnSTPTW7oOYmh69z4zL/gk3b+O6hoa733uviP82vnFcbqWlc9tDmZa23LVzaV1yXURi+JX+28NeBuj3+O8IrQ080Vm1eWB4OKjPmrJu7c1udWynvKF6/vs479lSW9+5gZkn+dKfellNGDPllzeULustz+A0bPvhgw7lkvEUwn/N4Ty7U7nhGsEpFkOfy+kutbOh1JQxhVDJumoW11hnkPThznh6FFlhfT+ra1x9sF56kx5YuDzVY9PQYAYA7iblw4frQ4TPCk2MK/xGU3rlmze62trHz6lsko+v+So/do74PT8KVkpJfOErKcv8znrMGsHTNxoEkWy1mYgDB6XBbPaWsuiS6CryGaL6zCjaXBgvtkuyXBua1wOKnh+k7L9AvPnYWffxK18FcJbuosGf3/Jo7amY+CE1vppzY+UTrva0FXc1i55pKQ/YjVL187N5fCn1kW5uot/1hi+DiZ+5atnJR9E+prvydJ9ZZ5mwOpU5gM4KYysMBQ71UzPuMTl9QQOyUo5nwioeYCPjFklrbK6s6X+ypUZ6rum9+CZYzWRiBJfSP0xzzSmrg7f86g0DKVj/wwFzieD9rRfPGFbeKMl05pn5j9/rsQJJ2iEgRrpohlyBo3f4QK7Kl+EcAYZgAoNVmZWXK704YAa3FwBxgSGUOs5htvGRz4Sgj3yFkSJFBuv/sxu5yk998T8WDJzvv/2RX19HtTUW1S+wpKRKRjJ6zzz/1/OPdFdWGlAKbvzS4PHOtURikg9AGz0LbIB85S/cPOpoXvuue8/iV2H1vPTy3ddvOeZ37HGmO3OmSzVzR+NS53+84dHlFhXPLqtzSO+5ruHM2vXtBdxP87LOzKAD359j/INYIbyPabIi3Cq6Wa+SaGe78diIzu7qcblcAa6/fJRvNopXFJnO+U9KKM5bqH5LM0iQSVmpPCPDu7ZT4Aoubz3709EBTyrTDjyx8MQXgUH1nqm7TWng4TzE4i4AsKskBITXfSyC4Fkl5MxnJDiKSIDSJAsGvd1y+/eNDp2e+A+5d8HeiiunrTkT6TqWLIs+/QRoWr98s0qj8uuzLuS22Ytufg3rdTaHn1m46sfgGKHXt0MGnLaRHdnwN37tvHcWKo2V6lnPxL4UvUQcRdOzmZSQs8X5CH5OxXMXpkATuDz8Et0SH4uyCRR+TjmBDP1GvsVrWEGVzEj33YVQ9jAtIKpqsl/s/0xrocwAAeJxjYGRgYADig3cEzsTz23xl4GZnAIHLRucNkWl2BrA4BwMTiAIAF4IITwB4nGNgZGBgZwCChWASxGZkQAXyABOUANh4nGNnYGBgHyAMADa8ANoAAAAAAAAOAFAAZgCyAMYA5gEeAUgBdAGcAfICLgKOAroDCgOOA7AD6gQ4BHwEuAToBQwFogXoBjYGbAbaB3IHqHicY2BkYGCQZ8hlYGcAASYg5gJCBob/YD4DABbVAaoAeJxdkE1qg0AYhl8Tk9AIoVDaVSmzahcF87PMARLIMoFAl0ZHY1BHdBJIT9AT9AQ9RQ9Qeqy+yteNMzDzfM+88w0K4BY/cNAMB6N2bUaPPBLukybCLvleeAAPj8JD+hfhMV7hC3u4wxs7OO4NzQSZcI/8Ltwnfwi75E/hAR7wJTyk/xYeY49fYQ/PztM+jbTZ7LY6OWdBJdX/pqs6NYWa+zMxa13oKrA6Uoerqi/JwtpYxZXJ1coUVmeZUWVlTjq0/tHacjmdxuL90OR8O0UEDYMNdtiSEpz5XQGqzlm30kzUdAYFFOb8R7NOZk0q2lwAyz1i7oAr1xoXvrOgtYhZx8wY5KRV269JZ5yGpmzPTjQhvY9je6vEElPOuJP3mWKnP5M3V+YAAAB4nG2P2XLCMAxFfYFspGUp3Te+IB9lHJF4cOzUS2n/voaEGR6qB+lKo+WITdhga/a/bRnDBFPMkCBFhhwF5ihxg1sssMQKa9xhg3s84BFPeMYLXvGGd3zgE9tZr/hveXKVkFYoSnoeHJXfRoWOqi54mo9ameNFdrK+dLSyaVf7oJQTlkhXpD3Z5XXhR/rUfQVuKXO91Jps4cLOS6/I5YL3XhodRRsVWZe4NnZOhWnSAWgxhMoEr6SmzZieF43Mk7ZOBdeCVGrp9Eu+54J2xhySplfB5XHwQLXUmT9KH6+kPnQ7ZYuIEzNyfs1DLU1VU4SWZ6LkXGHsD1ZKbMw=) format("woff"), url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAAKAIAAAwAgT1MvMlGRXgUAAAEoAAAAVmNtYXDiMBC2AAAB/AAAAUpnbHlmW/HoBwAAA4gAAA9QaGVhZAyD6V8AAADQAAAANmhoZWEOogcgAAAArAAAACRobXR42QAAAAAAAYAAAAB8bG9jYTDINOoAAANIAAAAQG1heHABMAB5AAABCAAAACBuYW1l1cf1oAAAEtgAAAIKcG9zdGZEjeMAABTkAAABgQABAAAHAAAAAKEHAAAAAAAHAAABAAAAAAAAAAAAAAAAAAAAHwABAAAAAQAAwdxheF8PPPUACwcAAAAAANMyzzEAAAAA0zLPMQAAAAAHAAcAAAAACAACAAAAAAAAAAEAAAAfAG0ABwAAAAAAAgAAAAoACgAAAP8AAAAAAAAAAQcAAZAABQAIBHEE5gAAAPoEcQTmAAADXABXAc4AAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZABA8QHxHgcAAAAAoQcAAAAAAAABAAAAAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAAAAAMAAAADAAAAHAABAAAAAABEAAMAAQAAABwABAAoAAAABgAEAAEAAgAA8R7//wAAAADxAf//AAAPAAABAAAAAAAAAAABBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAFAAZgCyAMYA5gEeAUgBdAGcAfICLgKOAroDCgOOA7AD6gQ4BHwEuAToBQwFogXoBjYGbAbaB3IHqAABAAAAAAWLBYsAAgAAAREBAlUDNgWL++oCCwAAAwAAAAAGawZrAAIADgAaAAAJAhMEAAMSAAUkABMCAAEmACc2ADcWABcGAALrAcD+QJX+w/5aCAgBpgE9AT0BpggI/lr+w/3+rgYGAVL9/QFSBgb+rgIwAVABUAGbCP5a/sP+w/5aCAgBpgE9AT0BpvrIBgFS/f0BUgYG/q79/f6uAAAAAgAAAAAFQAWLAAMABwAAASERKQERIREBwAEr/tUCVQErAXUEFvvqBBYAAAAEAAAAAAYgBiAABgATACQAJwAAAS4BJxUXNjcGBxc+ATUmACcVFhIBBwEhESEBEQEGBxU+ATcXNwEHFwTQAWVVuAO7AidxJSgF/t/lpc77t18BYf6fASsBdQE+TF1OijuZX/1gnJwDgGSeK6W4GBhqW3FGnFT0AWM4mjT+9AHrX/6f/kD+iwH2/sI7HZoSRDGYXwSWnJwAAAEAAAAABKsF1gAFAAABESEBEQECCwEqAXb+igRg/kD+iwSq/osAAAACAAAAAAVmBdYABgAMAAABLgEnET4BAREhAREBBWUBZVRUZfwRASsBdf6LA4Bkniv9piueAUT+QP6LBKr+iwAAAwAAAAAGIAYPAAUADAAaAAATESEBEQEFLgEnET4BAxUWEhcGAgcVNgA3JgDgASsBdf6LAsUBZVVVZbqlzgMDzqXlASEFBf7fBGD+QP6LBKr+i+Bkniv9piueAvOaNP70tbX+9DSaOAFi9fUBYgAAAAQAAAAABYsFiwAFAAsAEQAXAAABIxEhNSMDMzUzNSEBIxUhESMDFTMVMxECC5YBduCWluD+igOA4AF2luDglgLr/oqWAgrglvyAlgF2AqCW4AF2AAQAAAAABYsFiwAFAAsAEQAXAAABMxUzESETIxUhESMBMzUzNSETNSMRITUBdeCW/org4AF2lgHAluD+ipaWAXYCVeABdgHAlgF2++rglgHA4P6KlgAAAAACAAAAAAXWBdYADwATAAABIQ4BBxEeARchPgE3ES4BAyERIQVA/IA/VQEBVT8DgD9VAQFVP/yAA4AF1QFVP/yAP1UBAVU/A4A/VfvsA4AAAAYAAAAABmsGawAHAAwAEwAbACAAKAAACQEmJw4BBwElLgEnAQUhATYSNyYFAQYCBxYXIQUeARcBMwEWFz4BNwECvgFkTlSH8GEBEgOONemh/u4C5f3QAXpcaAEB/BP+3VxoAQEOAjD95DXpoQESeP7dTlSH8GH+7gPwAmgSAQFYUP4nd6X2Pv4nS/1zZAEBk01NAfhk/v+TTUhLpfY+Adn+CBIBAVhQAdkAAAAFAAAAAAZrBdYADwATABcAGwAfAAABIQ4BBxEeARchPgE3ES4BASEVIQEhNSEFITUhNSE1IQXV+1ZAVAICVEAEqkBUAgJU+xYBKv7WAur9FgLqAcD+1gEq/RYC6gXVAVU//IA/VQEBVT8DgD9V/ayV/tWVlZWWlQADAAAAAAYgBdYADwAnAD8AAAEhDgEHER4BFyE+ATcRLgEBIzUjFTM1MxUUBgcjLgEnET4BNzMeARUFIzUjFTM1MxUOAQcjLgE1ETQ2NzMeARcFi/vqP1QCAlQ/BBY/VAICVP1rcJWVcCog4CAqAQEqIOAgKgILcJWVcAEqIOAgKiog4CAqAQXVAVU//IA/VQEBVT8DgD9V/fcl4CVKICoBASogASogKgEBKiBKJeAlSiAqAQEqIAEqICoBASogAAAGAAAAAAYgBPYAAwAHAAsADwATABcAABMzNSMRMzUjETM1IwEhNSERITUhERUhNeCVlZWVlZUBKwQV++sEFfvrBBUDNZb+QJUBwJX+QJb+QJUCVZWVAAAAAQAAAAAGIAZsAC4AAAEiBgcBNjQnAR4BMz4BNy4BJw4BBxQXAS4BIw4BBx4BFzI2NwEGBx4BFz4BNy4BBUArSh797AcHAg8eTixffwICf19ffwIH/fEeTixffwICf18sTh4CFAUBA3tcXHsDA3sCTx8bATcZNhkBNB0gAn9fX38CAn9fGxn+zRwgAn9fX38CIBz+yhcaXHsCAntcXXsAAAIAAAAABlkGawBDAE8AAAE2NCc3PgEnAy4BDwEmLwEuASchDgEPAQYHJyYGBwMGFh8BBhQXBw4BFxMeAT8BFh8BHgEXIT4BPwE2NxcWNjcTNiYnBS4BJz4BNx4BFw4BBasFBZ4KBgeWBxkNujpEHAMUD/7WDxQCHEU5ug0aB5UHBQudBQWdCwUHlQcaDbo5RRwCFA8BKg8UAhxFOboNGgeVBwUL/ThvlAIClG9vlAIClAM3JEokewkaDQEDDAkFSy0cxg4RAQERDsYcLUsFCQz+/QwbCXskSiR7CRoN/v0MCQVLLRzGDhEBAREOxhwtSwUJDAEDDBsJQQKUb2+UAgKUb2+UAAAAAAEAAAAABmsGawALAAATEgAFJAATAgAlBACVCAGmAT0BPQGmCAj+Wv7D/sP+WgOA/sP+WggIAaYBPQE9AaYICP5aAAAAAgAAAAAGawZrAAsAFwAAAQQAAxIABSQAEwIAASYAJzYANxYAFwYAA4D+w/5aCAgBpgE9AT0BpggI/lr+w/3+rgYGAVL9/QFSBgb+rgZrCP5a/sP+w/5aCAgBpgE9AT0BpvrIBgFS/f0BUgYG/q79/f6uAAADAAAAAAZrBmsACwAXACMAAAEEAAMSAAUkABMCAAEmACc2ADcWABcGAAMOAQcuASc+ATceAQOA/sP+WggIAaYBPQE9AaYICP5a/sP9/q4GBgFS/f0BUgYG/q4dAn9fX38CAn9fX38Gawj+Wv7D/sP+WggIAaYBPQE9Aab6yAYBUv39AVIGBv6u/f3+rgJPX38CAn9fX38CAn8AAAAEAAAAAAYgBiAADwAbACUAKQAAASEOAQcRHgEXIT4BNxEuAQEjNSMVIxEzFTM1OwEhHgEXEQ4BByE3MzUjBYv76j9UAgJUPwQWP1QCAlT9a3CVcHCVcJYBKiAqAQEqIP7WcJWVBiACVD/76j9UAgJUPwQWP1T8gpWVAcC7uwEqIP7WICoBcOAAAgAAAAAGawZrAAsAFwAAAQQAAxIABSQAEwIAEwcJAScJATcJARcBA4D+w/5aCAgBpgE9AT0BpggI/lo4af70/vRpAQv+9WkBDAEMaf71BmsI/lr+w/7D/loICAGmAT0BPQGm/BFpAQv+9WkBDAEMaf71AQtp/vQAAQAAAAAF1ga2ABYAAAERCQERHgEXDgEHLgEnIxYAFzYANyYAA4D+iwF1vv0FBf2+vv0FlQYBUf7+AVEGBv6vBYsBKv6L/osBKgT9v779BQX9vv7+rwYGAVH+/gFRAAAAAQAAAAAFPwcAABQAAAERIyIGHQEhAyMRIREjETM1NDYzMgU/nVY8ASUn/v7O///QrZMG9P74SEi9/tj9CQL3ASjaus0AAAAABAAAAAAGjgcAADAARQBgAGwAAAEUHgMVFAcGBCMiJicmNTQ2NzYlLgE1NDcGIyImNTQ2Nz4BMyEHIx4BFRQOAycyNjc2NTQuAiMiBgcGFRQeAxMyPgI1NC4BLwEmLwImIyIOAxUUHgIBMxUjFSM1IzUzNTMDH0BbWkAwSP7qn4TlOSVZSoMBESAfFS4WlMtIP03TcAGiioNKTDFFRjGSJlAaNSI/akAqURkvFCs9WTY6a1s3Dg8THgocJU4QIDVob1M2RnF9A2vV1WnU1GkD5CRFQ1CATlpTenNTYDxHUYouUhIqQCkkMQTBlFKaNkJAWD+MWkhzRztAPiEbOWY6hn1SJyE7ZS5nZ1I0/JcaNF4+GTAkGCMLFx04Ag4kOF07Rms7HQNsbNvbbNkAAwAAAAAGgAZsAAMADgAqAAABESERARYGKwEiJjQ2MhYBESERNCYjIgYHBhURIRIQLwEhFSM+AzMyFgHd/rYBXwFnVAJSZGemZASP/rdRVj9VFQv+twIBAQFJAhQqR2c/q9AEj/whA98BMkliYpNhYfzd/cgCEml3RTMeM/3XAY8B8DAwkCAwOB/jAAABAAAAAAaUBgAAMQAAAQYHFhUUAg4BBCMgJxYzMjcuAScWMzI3LgE9ARYXLgE1NDcWBBcmNTQ2MzIXNjcGBzYGlENfAUyb1v7SrP7x4SMr4bBpph8hHCsqcJNETkJOLHkBW8YIvYaMYG1gJWldBWhiRQ4cgv797rdtkQSKAn1hBQsXsXUEJgMsjlNYS5WzCiYkhr1mFTlzPwoAAAABAAAAAAWABwAAIgAAARcOAQcGLgM1ESM1PgQ3PgE7AREhFSERFB4CNzYFMFAXsFlorXBOIahIckQwFAUBBwT0AU3+sg0gQzBOAc/tIz4BAjhceHg6AiDXGlddb1ctBQf+WPz9+h40NR4BAgABAAAAAAaABoAASgAAARQCBCMiJzY/AR4BMzI+ATU0LgEjIg4DFRQWFxY/ATY3NicmNTQ2MzIWFRQGIyImNz4CNTQmIyIGFRQXAwYXJgI1NBIkIAQSBoDO/p/Rb2s7EzYUaj15vmh34o5ptn9bK1BNHggIBgIGETPRqZepiWs9Sg4IJRc2Mj5WGWMRBM7+zgFhAaIBYc4DgNH+n84gXUfTJzmJ8JZyyH46YH2GQ2ieIAwgHxgGFxQ9WpfZpIOq7lc9I3VZHzJCclVJMf5eRmtbAXzp0QFhzs7+nwAABwAAAAAHAATPAA4AFwAqAD0AUABaAF0AAAERNh4CBw4BBwYmIycmNxY2NzYmBxEUBRY2Nz4BNy4BJyMGHwEeARcOARcWNjc+ATcuAScjBh8BHgEXFAYXFjY3PgE3LgEnIwYfAR4BFw4BBTM/ARUzESMGAyUVJwMchM2UWwgNq4JHrQgBAapUaAoJcWMBfiIhDiMrAQJLMB0BBAokNAIBPmMiIQ4iLAECSzAeAQUKJDQBP2MiIQ4iLAECSzAeAQUKJDQBAT75g+5B4arNLNIBJ44ByQL9BQ9mvYCKwA8FBQMDwwJVTGdzBf6VB8IHNR08lld9uT4LCRA/qGNxvUwHNR08lld9uT4LCRA/qGNxvUwHNR08lld9uT4LCRA/qGNxvVJkAWUDDEf+tYP5AQAAAAEAAAAABiAGtgAbAAABBAADER4BFzMRITU2ADcWABcVIREzPgE3EQIAA4D+4v6FBwJ/X+D+1QYBJ97eAScG/tXgX38CB/6FBrUH/oX+4v32X38CAlWV3gEnBgb+2d6V/asCf18CCgEeAXsAAAAAEADGAAEAAAAAAAEABwAAAAEAAAAAAAIABwAHAAEAAAAAAAMABwAOAAEAAAAAAAQABwAVAAEAAAAAAAUACwAcAAEAAAAAAAYABwAnAAEAAAAAAAoAKwAuAAEAAAAAAAsAEwBZAAMAAQQJAAEADgBsAAMAAQQJAAIADgB6AAMAAQQJAAMADgCIAAMAAQQJAAQADgCWAAMAAQQJAAUAFgCkAAMAAQQJAAYADgC6AAMAAQQJAAoAVgDIAAMAAQQJAAsAJgEeVmlkZW9KU1JlZ3VsYXJWaWRlb0pTVmlkZW9KU1ZlcnNpb24gMS4wVmlkZW9KU0dlbmVyYXRlZCBieSBzdmcydHRmIGZyb20gRm9udGVsbG8gcHJvamVjdC5odHRwOi8vZm9udGVsbG8uY29tAFYAaQBkAGUAbwBKAFMAUgBlAGcAdQBsAGEAcgBWAGkAZABlAG8ASgBTAFYAaQBkAGUAbwBKAFMAVgBlAHIAcwBpAG8AbgAgADEALgAwAFYAaQBkAGUAbwBKAFMARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABzAHYAZwAyAHQAdABmACAAZgByAG8AbQAgAEYAbwBuAHQAZQBsAGwAbwAgAHAAcgBvAGoAZQBjAHQALgBoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAAAAAgAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAABAgEDAQQBBQEGAQcBCAEJAQoBCwEMAQ0BDgEPARABEQESARMBFAEVARYBFwEYARkBGgEbARwBHQEeAR8EcGxheQtwbGF5LWNpcmNsZQVwYXVzZQt2b2x1bWUtbXV0ZQp2b2x1bWUtbG93CnZvbHVtZS1taWQLdm9sdW1lLWhpZ2gQZnVsbHNjcmVlbi1lbnRlcg9mdWxsc2NyZWVuLWV4aXQGc3F1YXJlB3NwaW5uZXIJc3VidGl0bGVzCGNhcHRpb25zCGNoYXB0ZXJzBXNoYXJlA2NvZwZjaXJjbGUOY2lyY2xlLW91dGxpbmUTY2lyY2xlLWlubmVyLWNpcmNsZQJoZAZjYW5jZWwGcmVwbGF5CGZhY2Vib29rBWdwbHVzCGxpbmtlZGluB3R3aXR0ZXIGdHVtYmxyCXBpbnRlcmVzdBFhdWRpby1kZXNjcmlwdGlvbgVhdWRpbwAAAAAA) format("truetype"); + font-weight: normal; + font-style: normal; } + +.vjs-icon-play, .video-js .vjs-big-play-button .vjs-icon-placeholder:before, .video-js .vjs-play-control .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-play:before, .video-js .vjs-big-play-button .vjs-icon-placeholder:before, .video-js .vjs-play-control .vjs-icon-placeholder:before { + content: "\f101"; } + +.vjs-icon-play-circle { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-play-circle:before { + content: "\f102"; } + +.vjs-icon-pause, .video-js .vjs-play-control.vjs-playing .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-pause:before, .video-js .vjs-play-control.vjs-playing .vjs-icon-placeholder:before { + content: "\f103"; } + +.vjs-icon-volume-mute, .video-js .vjs-mute-control.vjs-vol-0 .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-volume-mute:before, .video-js .vjs-mute-control.vjs-vol-0 .vjs-icon-placeholder:before { + content: "\f104"; } + +.vjs-icon-volume-low, .video-js .vjs-mute-control.vjs-vol-1 .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-volume-low:before, .video-js .vjs-mute-control.vjs-vol-1 .vjs-icon-placeholder:before { + content: "\f105"; } + +.vjs-icon-volume-mid, .video-js .vjs-mute-control.vjs-vol-2 .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-volume-mid:before, .video-js .vjs-mute-control.vjs-vol-2 .vjs-icon-placeholder:before { + content: "\f106"; } + +.vjs-icon-volume-high, .video-js .vjs-mute-control .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-volume-high:before, .video-js .vjs-mute-control .vjs-icon-placeholder:before { + content: "\f107"; } + +.vjs-icon-fullscreen-enter, .video-js .vjs-fullscreen-control .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-fullscreen-enter:before, .video-js .vjs-fullscreen-control .vjs-icon-placeholder:before { + content: "\f108"; } + +.vjs-icon-fullscreen-exit, .video-js.vjs-fullscreen .vjs-fullscreen-control .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-fullscreen-exit:before, .video-js.vjs-fullscreen .vjs-fullscreen-control .vjs-icon-placeholder:before { + content: "\f109"; } + +.vjs-icon-square { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-square:before { + content: "\f10a"; } + +.vjs-icon-spinner { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-spinner:before { + content: "\f10b"; } + +.vjs-icon-subtitles, .video-js .vjs-subtitles-button .vjs-icon-placeholder, .video-js .vjs-subs-caps-button .vjs-icon-placeholder, +.video-js.video-js:lang(en-GB) .vjs-subs-caps-button .vjs-icon-placeholder, +.video-js.video-js:lang(en-IE) .vjs-subs-caps-button .vjs-icon-placeholder, +.video-js.video-js:lang(en-AU) .vjs-subs-caps-button .vjs-icon-placeholder, +.video-js.video-js:lang(en-NZ) .vjs-subs-caps-button .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-subtitles:before, .video-js .vjs-subtitles-button .vjs-icon-placeholder:before, .video-js .vjs-subs-caps-button .vjs-icon-placeholder:before, + .video-js.video-js:lang(en-GB) .vjs-subs-caps-button .vjs-icon-placeholder:before, + .video-js.video-js:lang(en-IE) .vjs-subs-caps-button .vjs-icon-placeholder:before, + .video-js.video-js:lang(en-AU) .vjs-subs-caps-button .vjs-icon-placeholder:before, + .video-js.video-js:lang(en-NZ) .vjs-subs-caps-button .vjs-icon-placeholder:before { + content: "\f10c"; } + +.vjs-icon-captions, .video-js .vjs-captions-button .vjs-icon-placeholder, .video-js:lang(en) .vjs-subs-caps-button .vjs-icon-placeholder, +.video-js:lang(fr-CA) .vjs-subs-caps-button .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-captions:before, .video-js .vjs-captions-button .vjs-icon-placeholder:before, .video-js:lang(en) .vjs-subs-caps-button .vjs-icon-placeholder:before, + .video-js:lang(fr-CA) .vjs-subs-caps-button .vjs-icon-placeholder:before { + content: "\f10d"; } + +.vjs-icon-chapters, .video-js .vjs-chapters-button .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-chapters:before, .video-js .vjs-chapters-button .vjs-icon-placeholder:before { + content: "\f10e"; } + +.vjs-icon-share { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-share:before { + content: "\f10f"; } + +.vjs-icon-cog { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-cog:before { + content: "\f110"; } + +.vjs-icon-circle, .video-js .vjs-play-progress, .video-js .vjs-volume-level { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-circle:before, .video-js .vjs-play-progress:before, .video-js .vjs-volume-level:before { + content: "\f111"; } + +.vjs-icon-circle-outline { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-circle-outline:before { + content: "\f112"; } + +.vjs-icon-circle-inner-circle { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-circle-inner-circle:before { + content: "\f113"; } + +.vjs-icon-hd { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-hd:before { + content: "\f114"; } + +.vjs-icon-cancel, .video-js .vjs-control.vjs-close-button .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-cancel:before, .video-js .vjs-control.vjs-close-button .vjs-icon-placeholder:before { + content: "\f115"; } + +.vjs-icon-replay, .video-js .vjs-play-control.vjs-ended .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-replay:before, .video-js .vjs-play-control.vjs-ended .vjs-icon-placeholder:before { + content: "\f116"; } + +.vjs-icon-facebook { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-facebook:before { + content: "\f117"; } + +.vjs-icon-gplus { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-gplus:before { + content: "\f118"; } + +.vjs-icon-linkedin { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-linkedin:before { + content: "\f119"; } + +.vjs-icon-twitter { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-twitter:before { + content: "\f11a"; } + +.vjs-icon-tumblr { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-tumblr:before { + content: "\f11b"; } + +.vjs-icon-pinterest { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-pinterest:before { + content: "\f11c"; } + +.vjs-icon-audio-description, .video-js .vjs-descriptions-button .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-audio-description:before, .video-js .vjs-descriptions-button .vjs-icon-placeholder:before { + content: "\f11d"; } + +.vjs-icon-audio, .video-js .vjs-audio-button .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-audio:before, .video-js .vjs-audio-button .vjs-icon-placeholder:before { + content: "\f11e"; } + +.video-js { + display: block; + vertical-align: top; + box-sizing: border-box; + color: #fff; + background-color: #000; + position: relative; + padding: 0; + font-size: 10px; + line-height: 1; + font-weight: normal; + font-style: normal; + font-family: Arial, Helvetica, sans-serif; } + .video-js:-moz-full-screen { + position: absolute; } + .video-js:-webkit-full-screen { + width: 100% !important; + height: 100% !important; } + +.video-js[tabindex="-1"] { + outline: none; } + +.video-js *, +.video-js *:before, +.video-js *:after { + box-sizing: inherit; } + +.video-js ul { + font-family: inherit; + font-size: inherit; + line-height: inherit; + list-style-position: outside; + margin-left: 0; + margin-right: 0; + margin-top: 0; + margin-bottom: 0; } + +.video-js.vjs-fluid, +.video-js.vjs-16-9, +.video-js.vjs-4-3 { + width: 100%; + max-width: 100%; + height: 0; } + +.video-js.vjs-16-9 { + padding-top: 56.25%; } + +.video-js.vjs-4-3 { + padding-top: 75%; } + +.video-js.vjs-fill { + width: 100%; + height: 100%; } + +.video-js .vjs-tech { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; } + +body.vjs-full-window { + padding: 0; + margin: 0; + height: 100%; + overflow-y: auto; } + +.vjs-full-window .video-js.vjs-fullscreen { + position: fixed; + overflow: hidden; + z-index: 1000; + left: 0; + top: 0; + bottom: 0; + right: 0; } + +.video-js.vjs-fullscreen { + width: 100% !important; + height: 100% !important; + padding-top: 0 !important; } + +.video-js.vjs-fullscreen.vjs-user-inactive { + cursor: none; } + +.vjs-hidden { + display: none !important; } + +.vjs-disabled { + opacity: 0.5; + cursor: default; } + +.video-js .vjs-offscreen { + height: 1px; + left: -9999px; + position: absolute; + top: 0; + width: 1px; } + +.vjs-lock-showing { + display: block !important; + opacity: 1; + visibility: visible; } + +.vjs-no-js { + padding: 20px; + color: #fff; + background-color: #000; + font-size: 18px; + font-family: Arial, Helvetica, sans-serif; + text-align: center; + width: 300px; + height: 150px; + margin: 0px auto; } + +.vjs-no-js a, +.vjs-no-js a:visited { + color: #66A8CC; } + +.video-js .vjs-big-play-button { + font-size: 3em; + line-height: 1.5em; + height: 1.5em; + width: 3em; + display: block; + position: absolute; + top: 10px; + left: 10px; + padding: 0; + cursor: pointer; + opacity: 1; + border: 0.06666em solid #fff; + background-color: #2B333F; + background-color: rgba(43, 51, 63, 0.7); + -webkit-border-radius: 0.3em; + -moz-border-radius: 0.3em; + border-radius: 0.3em; + -webkit-transition: all 0.4s; + -moz-transition: all 0.4s; + -ms-transition: all 0.4s; + -o-transition: all 0.4s; + transition: all 0.4s; } + +.vjs-big-play-centered .vjs-big-play-button { + top: 50%; + left: 50%; + margin-top: -0.75em; + margin-left: -1.5em; } + +.video-js:hover .vjs-big-play-button, +.video-js .vjs-big-play-button:focus { + border-color: #fff; + background-color: #73859f; + background-color: rgba(115, 133, 159, 0.5); + -webkit-transition: all 0s; + -moz-transition: all 0s; + -ms-transition: all 0s; + -o-transition: all 0s; + transition: all 0s; } + +.vjs-controls-disabled .vjs-big-play-button, +.vjs-has-started .vjs-big-play-button, +.vjs-using-native-controls .vjs-big-play-button, +.vjs-error .vjs-big-play-button { + display: none; } + +.vjs-has-started.vjs-paused.vjs-show-big-play-button-on-pause .vjs-big-play-button { + display: block; } + +.video-js button { + background: none; + border: none; + color: inherit; + display: inline-block; + overflow: visible; + font-size: inherit; + line-height: inherit; + text-transform: none; + text-decoration: none; + transition: none; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; } + +.video-js .vjs-control.vjs-close-button { + cursor: pointer; + height: 3em; + position: absolute; + right: 0; + top: 0.5em; + z-index: 2; } + +.video-js .vjs-modal-dialog { + background: rgba(0, 0, 0, 0.8); + background: -webkit-linear-gradient(-90deg, rgba(0, 0, 0, 0.8), rgba(255, 255, 255, 0)); + background: linear-gradient(180deg, rgba(0, 0, 0, 0.8), rgba(255, 255, 255, 0)); + overflow: auto; + box-sizing: content-box; } + +.video-js .vjs-modal-dialog > * { + box-sizing: border-box; } + +.vjs-modal-dialog .vjs-modal-dialog-content { + font-size: 1.2em; + line-height: 1.5; + padding: 20px 24px; + z-index: 1; } + +.vjs-menu-button { + cursor: pointer; } + +.vjs-menu-button.vjs-disabled { + cursor: default; } + +.vjs-workinghover .vjs-menu-button.vjs-disabled:hover .vjs-menu { + display: none; } + +.vjs-menu .vjs-menu-content { + display: block; + padding: 0; + margin: 0; + font-family: Arial, Helvetica, sans-serif; + overflow: auto; + box-sizing: content-box; } + +.vjs-menu .vjs-menu-content > * { + box-sizing: border-box; } + +.vjs-scrubbing .vjs-menu-button:hover .vjs-menu { + display: none; } + +.vjs-menu li { + list-style: none; + margin: 0; + padding: 0.2em 0; + line-height: 1.4em; + font-size: 1.2em; + text-align: center; + text-transform: lowercase; } + +.vjs-menu li.vjs-menu-item:focus, +.vjs-menu li.vjs-menu-item:hover { + background-color: #73859f; + background-color: rgba(115, 133, 159, 0.5); } + +.vjs-menu li.vjs-selected, +.vjs-menu li.vjs-selected:focus, +.vjs-menu li.vjs-selected:hover { + background-color: #fff; + color: #2B333F; } + +.vjs-menu li.vjs-menu-title { + text-align: center; + text-transform: uppercase; + font-size: 1em; + line-height: 2em; + padding: 0; + margin: 0 0 0.3em 0; + font-weight: bold; + cursor: default; } + +.vjs-menu-button-popup .vjs-menu { + display: none; + position: absolute; + bottom: 0; + width: 10em; + left: -3em; + height: 0em; + margin-bottom: 1.5em; + border-top-color: rgba(43, 51, 63, 0.7); } + +.vjs-menu-button-popup .vjs-menu .vjs-menu-content { + background-color: #2B333F; + background-color: rgba(43, 51, 63, 0.7); + position: absolute; + width: 100%; + bottom: 1.5em; + max-height: 15em; } + +.vjs-workinghover .vjs-menu-button-popup:hover .vjs-menu, +.vjs-menu-button-popup .vjs-menu.vjs-lock-showing { + display: block; } + +.video-js .vjs-menu-button-inline { + -webkit-transition: all 0.4s; + -moz-transition: all 0.4s; + -ms-transition: all 0.4s; + -o-transition: all 0.4s; + transition: all 0.4s; + overflow: hidden; } + +.video-js .vjs-menu-button-inline:before { + width: 2.222222222em; } + +.video-js .vjs-menu-button-inline:hover, +.video-js .vjs-menu-button-inline:focus, +.video-js .vjs-menu-button-inline.vjs-slider-active, +.video-js.vjs-no-flex .vjs-menu-button-inline { + width: 12em; } + +.vjs-menu-button-inline .vjs-menu { + opacity: 0; + height: 100%; + width: auto; + position: absolute; + left: 4em; + top: 0; + padding: 0; + margin: 0; + -webkit-transition: all 0.4s; + -moz-transition: all 0.4s; + -ms-transition: all 0.4s; + -o-transition: all 0.4s; + transition: all 0.4s; } + +.vjs-menu-button-inline:hover .vjs-menu, +.vjs-menu-button-inline:focus .vjs-menu, +.vjs-menu-button-inline.vjs-slider-active .vjs-menu { + display: block; + opacity: 1; } + +.vjs-no-flex .vjs-menu-button-inline .vjs-menu { + display: block; + opacity: 1; + position: relative; + width: auto; } + +.vjs-no-flex .vjs-menu-button-inline:hover .vjs-menu, +.vjs-no-flex .vjs-menu-button-inline:focus .vjs-menu, +.vjs-no-flex .vjs-menu-button-inline.vjs-slider-active .vjs-menu { + width: auto; } + +.vjs-menu-button-inline .vjs-menu-content { + width: auto; + height: 100%; + margin: 0; + overflow: hidden; } + +.video-js .vjs-control-bar { + display: none; + width: 100%; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 3.0em; + background-color: #2B333F; + background-color: rgba(43, 51, 63, 0.7); } + +.vjs-has-started .vjs-control-bar { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + visibility: visible; + opacity: 1; + -webkit-transition: visibility 0.1s, opacity 0.1s; + -moz-transition: visibility 0.1s, opacity 0.1s; + -ms-transition: visibility 0.1s, opacity 0.1s; + -o-transition: visibility 0.1s, opacity 0.1s; + transition: visibility 0.1s, opacity 0.1s; } + +.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar { + visibility: visible; + opacity: 0; + -webkit-transition: visibility 1s, opacity 1s; + -moz-transition: visibility 1s, opacity 1s; + -ms-transition: visibility 1s, opacity 1s; + -o-transition: visibility 1s, opacity 1s; + transition: visibility 1s, opacity 1s; } + +.vjs-controls-disabled .vjs-control-bar, +.vjs-using-native-controls .vjs-control-bar, +.vjs-error .vjs-control-bar { + display: none !important; } + +.vjs-audio.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar { + opacity: 1; + visibility: visible; } + +.vjs-has-started.vjs-no-flex .vjs-control-bar { + display: table; } + +.video-js .vjs-control { + position: relative; + text-align: center; + margin: 0; + padding: 0; + height: 100%; + width: 4em; + -webkit-box-flex: none; + -moz-box-flex: none; + -webkit-flex: none; + -ms-flex: none; + flex: none; } + +.vjs-button > .vjs-icon-placeholder:before { + font-size: 1.8em; + line-height: 1.67; } + +.video-js .vjs-control:focus:before, +.video-js .vjs-control:hover:before, +.video-js .vjs-control:focus { + text-shadow: 0em 0em 1em white; } + +.video-js .vjs-control-text { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; } + +.vjs-no-flex .vjs-control { + display: table-cell; + vertical-align: middle; } + +.video-js .vjs-custom-control-spacer { + display: none; } + +.video-js .vjs-progress-control { + cursor: pointer; + -webkit-box-flex: auto; + -moz-box-flex: auto; + -webkit-flex: auto; + -ms-flex: auto; + flex: auto; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; + min-width: 4em; } + +.vjs-live .vjs-progress-control { + display: none; } + +.vjs-no-flex .vjs-progress-control { + width: auto; } + +.video-js .vjs-progress-holder { + -webkit-box-flex: auto; + -moz-box-flex: auto; + -webkit-flex: auto; + -ms-flex: auto; + flex: auto; + -webkit-transition: all 0.2s; + -moz-transition: all 0.2s; + -ms-transition: all 0.2s; + -o-transition: all 0.2s; + transition: all 0.2s; + height: 0.3em; } + +.video-js .vjs-progress-control .vjs-progress-holder { + margin: 0 10px; } + +.video-js .vjs-progress-control:hover .vjs-progress-holder { + font-size: 1.666666666666666666em; } + +.video-js .vjs-progress-holder .vjs-play-progress, +.video-js .vjs-progress-holder .vjs-load-progress, +.video-js .vjs-progress-holder .vjs-load-progress div { + position: absolute; + display: block; + height: 100%; + margin: 0; + padding: 0; + width: 0; + left: 0; + top: 0; } + +.video-js .vjs-play-progress { + background-color: #fff; } + .video-js .vjs-play-progress:before { + font-size: 0.9em; + position: absolute; + right: -0.5em; + top: -0.333333333333333em; + z-index: 1; } + +.video-js .vjs-load-progress { + background: #bfc7d3; + background: rgba(115, 133, 159, 0.5); } + +.video-js .vjs-load-progress div { + background: white; + background: rgba(115, 133, 159, 0.75); } + +.video-js .vjs-time-tooltip { + background-color: #fff; + background-color: rgba(255, 255, 255, 0.8); + -webkit-border-radius: 0.3em; + -moz-border-radius: 0.3em; + border-radius: 0.3em; + color: #000; + float: right; + font-family: Arial, Helvetica, sans-serif; + font-size: 1em; + padding: 6px 8px 8px 8px; + pointer-events: none; + position: relative; + top: -3.4em; + visibility: hidden; + z-index: 1; } + +.video-js .vjs-progress-holder:focus .vjs-time-tooltip { + display: none; } + +.video-js .vjs-progress-control:hover .vjs-time-tooltip, +.video-js .vjs-progress-control:hover .vjs-progress-holder:focus .vjs-time-tooltip { + display: block; + font-size: 0.6em; + visibility: visible; } + +.video-js .vjs-progress-control .vjs-mouse-display { + display: none; + position: absolute; + width: 1px; + height: 100%; + background-color: #000; + z-index: 1; } + +.vjs-no-flex .vjs-progress-control .vjs-mouse-display { + z-index: 0; } + +.video-js .vjs-progress-control:hover .vjs-mouse-display { + display: block; } + +.video-js.vjs-user-inactive .vjs-progress-control .vjs-mouse-display { + visibility: hidden; + opacity: 0; + -webkit-transition: visibility 1s, opacity 1s; + -moz-transition: visibility 1s, opacity 1s; + -ms-transition: visibility 1s, opacity 1s; + -o-transition: visibility 1s, opacity 1s; + transition: visibility 1s, opacity 1s; } + +.video-js.vjs-user-inactive.vjs-no-flex .vjs-progress-control .vjs-mouse-display { + display: none; } + +.vjs-mouse-display .vjs-time-tooltip { + color: #fff; + background-color: #000; + background-color: rgba(0, 0, 0, 0.8); } + +.video-js .vjs-slider { + position: relative; + cursor: pointer; + padding: 0; + margin: 0 0.45em 0 0.45em; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-color: #73859f; + background-color: rgba(115, 133, 159, 0.5); } + +.video-js .vjs-slider:focus { + text-shadow: 0em 0em 1em white; + -webkit-box-shadow: 0 0 1em #fff; + -moz-box-shadow: 0 0 1em #fff; + box-shadow: 0 0 1em #fff; } + +.video-js .vjs-mute-control { + cursor: pointer; + -webkit-box-flex: none; + -moz-box-flex: none; + -webkit-flex: none; + -ms-flex: none; + flex: none; + padding-left: 2em; + padding-right: 2em; + padding-bottom: 3em; } + +.video-js .vjs-volume-control { + cursor: pointer; + margin-right: 1em; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; } + +.video-js .vjs-volume-control.vjs-volume-horizontal { + width: 5em; } + +.video-js .vjs-volume-panel .vjs-volume-control { + visibility: visible; + opacity: 0; + width: 1px; + height: 1px; + margin-left: -1px; } + +.vjs-no-flex .vjs-volume-panel .vjs-volume-control.vjs-volume-vertical { + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; } + .vjs-no-flex .vjs-volume-panel .vjs-volume-control.vjs-volume-vertical .vjs-volume-bar, + .vjs-no-flex .vjs-volume-panel .vjs-volume-control.vjs-volume-vertical .vjs-volume-level { + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; } + +.video-js .vjs-volume-panel { + -webkit-transition: width 1s; + -moz-transition: width 1s; + -ms-transition: width 1s; + -o-transition: width 1s; + transition: width 1s; } + .video-js .vjs-volume-panel:hover .vjs-volume-control, + .video-js .vjs-volume-panel:active .vjs-volume-control, + .video-js .vjs-volume-panel:focus .vjs-volume-control, + .video-js .vjs-volume-panel .vjs-volume-control:hover, + .video-js .vjs-volume-panel .vjs-volume-control:active, + .video-js .vjs-volume-panel .vjs-volume-control:focus, + .video-js .vjs-volume-panel .vjs-mute-control:hover ~ .vjs-volume-control, + .video-js .vjs-volume-panel .vjs-mute-control:active ~ .vjs-volume-control, + .video-js .vjs-volume-panel .vjs-mute-control:focus ~ .vjs-volume-control, + .video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active { + visibility: visible; + opacity: 1; + position: relative; + -webkit-transition: visibility 0.1s, opacity 0.1s, height 0.1s, width 0.1s, left 0s, top 0s; + -moz-transition: visibility 0.1s, opacity 0.1s, height 0.1s, width 0.1s, left 0s, top 0s; + -ms-transition: visibility 0.1s, opacity 0.1s, height 0.1s, width 0.1s, left 0s, top 0s; + -o-transition: visibility 0.1s, opacity 0.1s, height 0.1s, width 0.1s, left 0s, top 0s; + transition: visibility 0.1s, opacity 0.1s, height 0.1s, width 0.1s, left 0s, top 0s; } + .video-js .vjs-volume-panel:hover .vjs-volume-control.vjs-volume-horizontal, + .video-js .vjs-volume-panel:active .vjs-volume-control.vjs-volume-horizontal, + .video-js .vjs-volume-panel:focus .vjs-volume-control.vjs-volume-horizontal, + .video-js .vjs-volume-panel .vjs-volume-control:hover.vjs-volume-horizontal, + .video-js .vjs-volume-panel .vjs-volume-control:active.vjs-volume-horizontal, + .video-js .vjs-volume-panel .vjs-volume-control:focus.vjs-volume-horizontal, + .video-js .vjs-volume-panel .vjs-mute-control:hover ~ .vjs-volume-control.vjs-volume-horizontal, + .video-js .vjs-volume-panel .vjs-mute-control:active ~ .vjs-volume-control.vjs-volume-horizontal, + .video-js .vjs-volume-panel .vjs-mute-control:focus ~ .vjs-volume-control.vjs-volume-horizontal, + .video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active.vjs-volume-horizontal { + width: 5em; + height: 3em; } + .video-js .vjs-volume-panel:hover .vjs-volume-control.vjs-volume-vertical, + .video-js .vjs-volume-panel:active .vjs-volume-control.vjs-volume-vertical, + .video-js .vjs-volume-panel:focus .vjs-volume-control.vjs-volume-vertical, + .video-js .vjs-volume-panel .vjs-volume-control:hover.vjs-volume-vertical, + .video-js .vjs-volume-panel .vjs-volume-control:active.vjs-volume-vertical, + .video-js .vjs-volume-panel .vjs-volume-control:focus.vjs-volume-vertical, + .video-js .vjs-volume-panel .vjs-mute-control:hover ~ .vjs-volume-control.vjs-volume-vertical, + .video-js .vjs-volume-panel .vjs-mute-control:active ~ .vjs-volume-control.vjs-volume-vertical, + .video-js .vjs-volume-panel .vjs-mute-control:focus ~ .vjs-volume-control.vjs-volume-vertical, + .video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active.vjs-volume-vertical { + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; } + .video-js .vjs-volume-panel:hover .vjs-volume-control.vjs-volume-vertical .vjs-volume-bar, + .video-js .vjs-volume-panel:hover .vjs-volume-control.vjs-volume-vertical .vjs-volume-level, + .video-js .vjs-volume-panel:active .vjs-volume-control.vjs-volume-vertical .vjs-volume-bar, + .video-js .vjs-volume-panel:active .vjs-volume-control.vjs-volume-vertical .vjs-volume-level, + .video-js .vjs-volume-panel:focus .vjs-volume-control.vjs-volume-vertical .vjs-volume-bar, + .video-js .vjs-volume-panel:focus .vjs-volume-control.vjs-volume-vertical .vjs-volume-level, + .video-js .vjs-volume-panel .vjs-volume-control:hover.vjs-volume-vertical .vjs-volume-bar, + .video-js .vjs-volume-panel .vjs-volume-control:hover.vjs-volume-vertical .vjs-volume-level, + .video-js .vjs-volume-panel .vjs-volume-control:active.vjs-volume-vertical .vjs-volume-bar, + .video-js .vjs-volume-panel .vjs-volume-control:active.vjs-volume-vertical .vjs-volume-level, + .video-js .vjs-volume-panel .vjs-volume-control:focus.vjs-volume-vertical .vjs-volume-bar, + .video-js .vjs-volume-panel .vjs-volume-control:focus.vjs-volume-vertical .vjs-volume-level, + .video-js .vjs-volume-panel .vjs-mute-control:hover ~ .vjs-volume-control.vjs-volume-vertical .vjs-volume-bar, + .video-js .vjs-volume-panel .vjs-mute-control:hover ~ .vjs-volume-control.vjs-volume-vertical .vjs-volume-level, + .video-js .vjs-volume-panel .vjs-mute-control:active ~ .vjs-volume-control.vjs-volume-vertical .vjs-volume-bar, + .video-js .vjs-volume-panel .vjs-mute-control:active ~ .vjs-volume-control.vjs-volume-vertical .vjs-volume-level, + .video-js .vjs-volume-panel .vjs-mute-control:focus ~ .vjs-volume-control.vjs-volume-vertical .vjs-volume-bar, + .video-js .vjs-volume-panel .vjs-mute-control:focus ~ .vjs-volume-control.vjs-volume-vertical .vjs-volume-level, + .video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active.vjs-volume-vertical .vjs-volume-bar, + .video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active.vjs-volume-vertical .vjs-volume-level { + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; } + .video-js .vjs-volume-panel.vjs-volume-panel-horizontal:hover, .video-js .vjs-volume-panel.vjs-volume-panel-horizontal:focus, .video-js .vjs-volume-panel.vjs-volume-panel-horizontal:active, .video-js .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active { + width: 9em; + -webkit-transition: width 0.1s; + -moz-transition: width 0.1s; + -ms-transition: width 0.1s; + -o-transition: width 0.1s; + transition: width 0.1s; } + +.video-js .vjs-volume-panel .vjs-volume-control.vjs-volume-vertical { + height: 8em; + width: 3em; + left: -3.5em; + -webkit-transition: visibility 1s, opacity 1s, height 1s 1s, width 1s 1s, left 1s 1s, top 1s 1s; + -moz-transition: visibility 1s, opacity 1s, height 1s 1s, width 1s 1s, left 1s 1s, top 1s 1s; + -ms-transition: visibility 1s, opacity 1s, height 1s 1s, width 1s 1s, left 1s 1s, top 1s 1s; + -o-transition: visibility 1s, opacity 1s, height 1s 1s, width 1s 1s, left 1s 1s, top 1s 1s; + transition: visibility 1s, opacity 1s, height 1s 1s, width 1s 1s, left 1s 1s, top 1s 1s; } + +.video-js .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal { + -webkit-transition: visibility 1s, opacity 1s, height 1s 1s, width 1s, left 1s 1s, top 1s 1s; + -moz-transition: visibility 1s, opacity 1s, height 1s 1s, width 1s, left 1s 1s, top 1s 1s; + -ms-transition: visibility 1s, opacity 1s, height 1s 1s, width 1s, left 1s 1s, top 1s 1s; + -o-transition: visibility 1s, opacity 1s, height 1s 1s, width 1s, left 1s 1s, top 1s 1s; + transition: visibility 1s, opacity 1s, height 1s 1s, width 1s, left 1s 1s, top 1s 1s; } + +.video-js.vjs-no-flex .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal { + width: 5em; + height: 3em; + visibility: visible; + opacity: 1; + position: relative; + -webkit-transition: none; + -moz-transition: none; + -ms-transition: none; + -o-transition: none; + transition: none; } + +.video-js.vjs-no-flex .vjs-volume-control.vjs-volume-vertical, +.video-js.vjs-no-flex .vjs-volume-panel .vjs-volume-control.vjs-volume-vertical { + position: absolute; + bottom: 3em; + left: 0.5em; } + +.video-js .vjs-volume-panel { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; } + +.video-js .vjs-volume-bar { + margin: 1.35em 0.45em; } + +.vjs-volume-bar.vjs-slider-horizontal { + width: 5em; + height: 0.3em; } + +.vjs-volume-bar.vjs-slider-vertical { + width: 0.3em; + height: 5em; + margin: 1.35em auto; } + +.video-js .vjs-volume-level { + position: absolute; + bottom: 0; + left: 0; + background-color: #fff; } + .video-js .vjs-volume-level:before { + position: absolute; + font-size: 0.9em; } + +.vjs-slider-vertical .vjs-volume-level { + width: 0.3em; } + .vjs-slider-vertical .vjs-volume-level:before { + top: -0.5em; + left: -0.3em; } + +.vjs-slider-horizontal .vjs-volume-level { + height: 0.3em; } + .vjs-slider-horizontal .vjs-volume-level:before { + top: -0.3em; + right: -0.5em; } + +.video-js .vjs-volume-panel.vjs-volume-panel-vertical { + width: 4em; } + +.vjs-volume-bar.vjs-slider-vertical .vjs-volume-level { + height: 100%; } + +.vjs-volume-bar.vjs-slider-horizontal .vjs-volume-level { + width: 100%; } + +.video-js .vjs-volume-vertical { + width: 3em; + height: 8em; + bottom: 8em; + background-color: #2B333F; + background-color: rgba(43, 51, 63, 0.7); } + +.video-js .vjs-volume-horizontal .vjs-menu { + left: -2em; } + +.vjs-poster { + display: inline-block; + vertical-align: middle; + background-repeat: no-repeat; + background-position: 50% 50%; + background-size: contain; + background-color: #000000; + cursor: pointer; + margin: 0; + padding: 0; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + height: 100%; } + +.vjs-poster img { + display: block; + vertical-align: middle; + margin: 0 auto; + max-height: 100%; + padding: 0; + width: 100%; } + +.vjs-has-started .vjs-poster { + display: none; } + +.vjs-audio.vjs-has-started .vjs-poster { + display: block; } + +.vjs-using-native-controls .vjs-poster { + display: none; } + +.video-js .vjs-live-control { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-align: flex-start; + -webkit-align-items: flex-start; + -ms-flex-align: flex-start; + align-items: flex-start; + -webkit-box-flex: auto; + -moz-box-flex: auto; + -webkit-flex: auto; + -ms-flex: auto; + flex: auto; + font-size: 1em; + line-height: 3em; } + +.vjs-no-flex .vjs-live-control { + display: table-cell; + width: auto; + text-align: left; } + +.video-js .vjs-time-control { + -webkit-box-flex: none; + -moz-box-flex: none; + -webkit-flex: none; + -ms-flex: none; + flex: none; + font-size: 1em; + line-height: 3em; + min-width: 2em; + width: auto; + padding-left: 1em; + padding-right: 1em; } + +.vjs-live .vjs-time-control { + display: none; } + +.video-js .vjs-current-time, +.vjs-no-flex .vjs-current-time { + display: none; } + +.vjs-no-flex .vjs-remaining-time.vjs-time-control.vjs-control { + width: 0px !important; + white-space: nowrap; } + +.video-js .vjs-duration, +.vjs-no-flex .vjs-duration { + display: none; } + +.vjs-time-divider { + display: none; + line-height: 3em; } + +.vjs-live .vjs-time-divider { + display: none; } + +.video-js .vjs-play-control .vjs-icon-placeholder { + cursor: pointer; + -webkit-box-flex: none; + -moz-box-flex: none; + -webkit-flex: none; + -ms-flex: none; + flex: none; } + +.vjs-text-track-display { + position: absolute; + bottom: 3em; + left: 0; + right: 0; + top: 0; + pointer-events: none; } + +.video-js.vjs-user-inactive.vjs-playing .vjs-text-track-display { + bottom: 1em; } + +.video-js .vjs-text-track { + font-size: 1.4em; + text-align: center; + margin-bottom: 0.1em; + background-color: #000; + background-color: rgba(0, 0, 0, 0.5); } + +.vjs-subtitles { + color: #fff; } + +.vjs-captions { + color: #fc6; } + +.vjs-tt-cue { + display: block; } + +video::-webkit-media-text-track-display { + -moz-transform: translateY(-3em); + -ms-transform: translateY(-3em); + -o-transform: translateY(-3em); + -webkit-transform: translateY(-3em); + transform: translateY(-3em); } + +.video-js.vjs-user-inactive.vjs-playing video::-webkit-media-text-track-display { + -moz-transform: translateY(-1.5em); + -ms-transform: translateY(-1.5em); + -o-transform: translateY(-1.5em); + -webkit-transform: translateY(-1.5em); + transform: translateY(-1.5em); } + +.video-js .vjs-fullscreen-control { + cursor: pointer; + -webkit-box-flex: none; + -moz-box-flex: none; + -webkit-flex: none; + -ms-flex: none; + flex: none; } + +.vjs-playback-rate .vjs-playback-rate-value { + font-size: 1.5em; + line-height: 2; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + text-align: center; } + +.vjs-playback-rate .vjs-menu { + width: 4em; + left: 0em; } + +.vjs-error .vjs-error-display .vjs-modal-dialog-content { + font-size: 1.4em; + text-align: center; } + +.vjs-error .vjs-error-display:before { + color: #fff; + content: 'X'; + font-family: Arial, Helvetica, sans-serif; + font-size: 4em; + left: 0; + line-height: 1; + margin-top: -0.5em; + position: absolute; + text-shadow: 0.05em 0.05em 0.1em #000; + text-align: center; + top: 50%; + vertical-align: middle; + width: 100%; } + +.vjs-loading-spinner { + display: none; + position: absolute; + top: 50%; + left: 50%; + margin: -25px 0 0 -25px; + opacity: 0.85; + text-align: left; + border: 6px solid rgba(43, 51, 63, 0.7); + box-sizing: border-box; + background-clip: padding-box; + width: 50px; + height: 50px; + border-radius: 25px; } + +.vjs-seeking .vjs-loading-spinner, +.vjs-waiting .vjs-loading-spinner { + display: block; } + +.vjs-loading-spinner:before, +.vjs-loading-spinner:after { + content: ""; + position: absolute; + margin: -6px; + box-sizing: inherit; + width: inherit; + height: inherit; + border-radius: inherit; + opacity: 1; + border: inherit; + border-color: transparent; + border-top-color: white; } + +.vjs-seeking .vjs-loading-spinner:before, +.vjs-seeking .vjs-loading-spinner:after, +.vjs-waiting .vjs-loading-spinner:before, +.vjs-waiting .vjs-loading-spinner:after { + -webkit-animation: vjs-spinner-spin 1.1s cubic-bezier(0.6, 0.2, 0, 0.8) infinite, vjs-spinner-fade 1.1s linear infinite; + animation: vjs-spinner-spin 1.1s cubic-bezier(0.6, 0.2, 0, 0.8) infinite, vjs-spinner-fade 1.1s linear infinite; } + +.vjs-seeking .vjs-loading-spinner:before, +.vjs-waiting .vjs-loading-spinner:before { + border-top-color: white; } + +.vjs-seeking .vjs-loading-spinner:after, +.vjs-waiting .vjs-loading-spinner:after { + border-top-color: white; + -webkit-animation-delay: 0.44s; + animation-delay: 0.44s; } + +@keyframes vjs-spinner-spin { + 100% { + transform: rotate(360deg); } } + +@-webkit-keyframes vjs-spinner-spin { + 100% { + -webkit-transform: rotate(360deg); } } + +@keyframes vjs-spinner-fade { + 0% { + border-top-color: #73859f; } + 20% { + border-top-color: #73859f; } + 35% { + border-top-color: white; } + 60% { + border-top-color: #73859f; } + 100% { + border-top-color: #73859f; } } + +@-webkit-keyframes vjs-spinner-fade { + 0% { + border-top-color: #73859f; } + 20% { + border-top-color: #73859f; } + 35% { + border-top-color: white; } + 60% { + border-top-color: #73859f; } + 100% { + border-top-color: #73859f; } } + +.vjs-chapters-button .vjs-menu ul { + width: 24em; } + +.video-js .vjs-subs-caps-button + .vjs-menu .vjs-captions-menu-item .vjs-menu-item-text .vjs-icon-placeholder { + position: absolute; } + +.video-js .vjs-subs-caps-button + .vjs-menu .vjs-captions-menu-item .vjs-menu-item-text .vjs-icon-placeholder:before { + font-family: VideoJS; + content: "\f10d"; + font-size: 1.5em; + line-height: inherit; } + +.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-custom-control-spacer { + -webkit-box-flex: auto; + -moz-box-flex: auto; + -webkit-flex: auto; + -ms-flex: auto; + flex: auto; } + +.video-js.vjs-layout-tiny:not(.vjs-fullscreen).vjs-no-flex .vjs-custom-control-spacer { + width: auto; } + +.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-current-time, .video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-time-divider, .video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-duration, .video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-remaining-time, +.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-playback-rate, .video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-progress-control, +.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-mute-control, .video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-volume-control, +.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-chapters-button, .video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-descriptions-button, .video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-captions-button, +.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-subtitles-button, .video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-audio-button { + display: none; } + +.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-current-time, .video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-time-divider, .video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-duration, .video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-remaining-time, +.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-playback-rate, +.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-mute-control, .video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-volume-control, +.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-chapters-button, .video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-descriptions-button, .video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-captions-button, +.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-subtitles-button, .video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-audio-button { + display: none; } + +.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-current-time, .video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-time-divider, .video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-duration, .video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-remaining-time, +.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-playback-rate, +.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-mute-control, .video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-volume-control, +.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-chapters-button, .video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-descriptions-button, .video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-captions-button, +.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-subtitles-button .vjs-audio-button { + display: none; } + +.vjs-modal-dialog.vjs-text-track-settings { + background-color: #2B333F; + background-color: rgba(43, 51, 63, 0.75); + color: #fff; + height: 70%; } + +.vjs-text-track-settings .vjs-modal-dialog-content { + display: table; } + +.vjs-text-track-settings .vjs-track-settings-colors, +.vjs-text-track-settings .vjs-track-settings-font, +.vjs-text-track-settings .vjs-track-settings-controls { + display: table-cell; } + +.vjs-text-track-settings .vjs-track-settings-controls { + text-align: right; + vertical-align: bottom; } + +.vjs-text-track-settings fieldset { + margin: 5px; + padding: 3px; + border: none; } + +.vjs-text-track-settings fieldset span { + display: inline-block; + margin-left: 5px; } + +.vjs-text-track-settings legend { + color: #fff; + margin: 0 0 5px 0; } + +.vjs-text-track-settings .vjs-label { + position: absolute; + clip: rect(1px 1px 1px 1px); + clip: rect(1px, 1px, 1px, 1px); + display: block; + margin: 0 0 5px 0; + padding: 0; + border: 0; + height: 1px; + width: 1px; + overflow: hidden; } + +.vjs-track-settings-controls button:focus, +.vjs-track-settings-controls button:active { + outline-style: solid; + outline-width: medium; + background-image: linear-gradient(0deg, #fff 88%, #73859f 100%); } + +.vjs-track-settings-controls button:hover { + color: rgba(43, 51, 63, 0.75); } + +.vjs-track-settings-controls button { + background-color: #fff; + background-image: linear-gradient(-180deg, #fff 88%, #73859f 100%); + color: #2B333F; + cursor: pointer; + border-radius: 2px; } + +.vjs-track-settings-controls .vjs-default-button { + margin-right: 1em; } + +@media print { + .video-js > *:not(.vjs-tech):not(.vjs-poster) { + visibility: hidden; } } + +@media \0screen { + .vjs-user-inactive.vjs-playing .vjs-control-bar :before { + content: ""; + } +} + +@media \0screen { + .vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar { + visibility: hidden; + } +} diff --git a/server/user.jobengine.osgi.server/css/video-js.css b/server/user.jobengine.osgi.server/css/video-js.css index 33c9f56e..a7469e83 100644 --- a/server/user.jobengine.osgi.server/css/video-js.css +++ b/server/user.jobengine.osgi.server/css/video-js.css @@ -10,11 +10,11 @@ @font-face { font-family: VideoJS; - src: url("../font/2.0.0/VideoJS.eot?#iefix") format("eot"); } + src: url("font/VideoJS.eot?#iefix") format("eot"); } @font-face { font-family: VideoJS; - src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAA54AAoAAAAAFmgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAAA9AAAAD4AAABWUZFeBWNtYXAAAAE0AAAAOgAAAUriMBC2Z2x5ZgAAAXAAAAouAAAPUFvx6AdoZWFkAAALoAAAACsAAAA2DIPpX2hoZWEAAAvMAAAAGAAAACQOogcgaG10eAAAC+QAAAAPAAAAfNkAAABsb2NhAAAL9AAAAEAAAABAMMg06m1heHAAAAw0AAAAHwAAACABMAB5bmFtZQAADFQAAAElAAACCtXH9aBwb3N0AAANfAAAAPwAAAGBZkSN43icY2BkZ2CcwMDKwMFSyPKMgYHhF4RmjmEIZzzHwMDEwMrMgBUEpLmmMDh8ZPwoxw7iLmSHCDOCCADvEAo+AAB4nGNgYGBmgGAZBkYGEHAB8hjBfBYGDSDNBqQZGZgYGD7K/f8PUvCREUTzM0DVAwEjG8OIBwCPdwbVAAB4nI1Xe1CU1xX/zv1eLItLln0JwrIfC7sJGET2hRJ2N1GUoBJE8AESQEEhmBHjaB7UuBMTO4GMaSu7aY3RNlOdRPNqO2pqRmuTaSZtR6JJILUZk00a/4imjpmiecB303O/XUgMJOPufvd+99xzzz33nN855y4HHH7EfrGfIxwHRiANvF/sH71I9BzHszmpW+rGOQOXxXE6YhI4PoMT8zkT4cDFuf1cwMrZJI5cglM0HKVv0MaUFDgIFfg9mJJCG+kbKn1JkqBOVaFOkuhLpARq8fu0Nnc9/zdvfY9PxXW4PdH0C6N+PCejhorxFjAqRjgFRXSINEARbBGsoxcFK7IJmr4OycFJnInL59zIXwxui80fkGRbEHyosMWaATJKUfCskmwJQsAWANkmnIGOhlf514h7U8HNIv3owoHB0WMt0Eb3sx0guLi5pq/8Ny1q6969fKR9X9GBV6dPv6dp04K99SOwtmyPl47ApRa6n4ZpP1yjr5fn7MmYP/vXLUJs715UguklHBaHOZHZmG1N9FAIW2mf0MqWCIdo/8RZ1yGfxKUldDcGIbFA7ICO+vqOMSPTh/ZrSqgHi/bB/O8E8Mnzp+M+acxfpsTShBwej26TiGxBn7m4eEIO+Rueu6Hj+IFBnh88cAEUEQ//nVLx5C7kf+yIR47QEe+eMlhz9SqsGbe3hh2R03NGzoY6O42Kz8l7fB6fAk6LYnTyFo/FYyT6GGyNx2Jx2sdH4rA1Fo/HyCXaFyOp8dhYBCfJb2NIn1ImE6CYNGmgSTb52DawJR6jfXEmDU4xyTEmpgHHOIStoxfjSGdkbsK2w2jbdMQG4sgAstEONgURYCwGHhEhhscioQaAhhCf7McifEQc0l6+mxj9nI+gmSdiQ0Zbm7gZnIO7GSMEXG6UDAVocxAV8GcEXCKg1a02RcTtwANWRGIAyElor6n/+ZU2yOB3+T77Hb1MLqhn4KHVnQBjJnqe9QZSon6Kc5DxAD2vMdPL/BXSmQGwspa67z9wLUjdi9TN7QC7lyyBr9rpt7uXVC1CMpyjKRoXnGPHTuiaPLsNdc2dbAFQLAooPkXEh33FodHl4XpC6sPCIa0ftUIhHSYXVSu5iME+DIXsbZJ51BeidCgajcai43jU9nVzoSn2dPqcFvSoxSzJzgRKAx47WMRxOrIj3Wf0+hndxhJTiOkSEqxar3b3RKM9hY64oxBA64ieURLvCfpkDb8siBdUJ1bgT+urJ5PGfewQrmm5R5+0HmfyIPySD7OYkT0WxRePah8oEiyjlxIP74thVoRTURpmL6QhGuWS+QDjdANXjIM8SQa/1w128ODx0Qp4aLMNg9+JL3joUn8AMxW+aLNiuKjarn4uyyTdXjOzZTsh21uwldUvJoYza+zELALfu3p1L8/3krtyZ0Ag058J3hxHghvbGZn0dHZy6Mim/7Blre4lpHd1c28yVqRViO153F2oIWoXCIKbL4Z0cM1iaQn9mI5KuV2SzEvWXJDMNtkANpMdQoDDhIdD4A/YrP6Aye9ysxyE+uOEAcTDorgvVZJjcua043PnZ/PmdDqcbibZlXOOT8uSo7Kof0YUn9GL+Jo17ficymxiTofC6znUso0DhAxs1Fo+kF+d36vLmgZ8mk5cdGv2mwYj5k3Dm9m3LhJ1aVRNm6HrTbLgYAoWXDhDd/u4PGy5CT+xGMdiaBovewUCF/1BiWNljI9MLn7jeScpg+WyH6mfU62eVDql7hsrmvx1ezp/YldE2LhjbkiDnAn8tGy/MW3IXRMYJduvq9HpmIcKuFt+JCtgdGEGKAcF6UacVwIYbVPGfw/+YuNBS4cx/CUHcnyfc+wRDMtTr72mMSBjT/yn/GKSdeDWQUCH6Xoqq5R10RE60gV6erUL0iCti16d0hZjxut4QI/rEpgSh6WjnJXdBXRg1GKCucGJPtFqM27aD1tOqqKonsQ2KsFSSmEpmvRlsR+TcD9OFwrqXxIclL4sJTnGMSuG8KpkZvKdeVIOKDyWSyPLV16/p1QMPbP8NihwUzr47bdnXtwtjdCvqqpO0H+pOvIl3Pzv46e5CT/tQjklXCXXym1AaWY7bzHLkuDMc7ldKCvgxzLn8wYkJLBhEDyK7MT8bTbwbkxbfp+3mKAGsmTBpabSIEECzMIcQlzOPAMKsxMs7uhsnxPLuofPDTc1hkuq6MX9j16YU7CqegcYHbmWYuvAP6tCS97tgWf7dlQvnl25YPavXLVZvrzQPeHCpZmzzEUVq/xzu5sChnSTPTW7oOYmh69z4zL/gk3b+O6hoa733uviP82vnFcbqWlc9tDmZa23LVzaV1yXURi+JX+28NeBuj3+O8IrQ080Vm1eWB4OKjPmrJu7c1udWynvKF6/vs479lSW9+5gZkn+dKfellNGDPllzeULustz+A0bPvhgw7lkvEUwn/N4Ty7U7nhGsEpFkOfy+kutbOh1JQxhVDJumoW11hnkPThznh6FFlhfT+ra1x9sF56kx5YuDzVY9PQYAYA7iblw4frQ4TPCk2MK/xGU3rlmze62trHz6lsko+v+So/do74PT8KVkpJfOErKcv8znrMGsHTNxoEkWy1mYgDB6XBbPaWsuiS6CryGaL6zCjaXBgvtkuyXBua1wOKnh+k7L9AvPnYWffxK18FcJbuosGf3/Jo7amY+CE1vppzY+UTrva0FXc1i55pKQ/YjVL187N5fCn1kW5uot/1hi+DiZ+5atnJR9E+prvydJ9ZZ5mwOpU5gM4KYysMBQ71UzPuMTl9QQOyUo5nwioeYCPjFklrbK6s6X+ypUZ6rum9+CZYzWRiBJfSP0xzzSmrg7f86g0DKVj/wwFzieD9rRfPGFbeKMl05pn5j9/rsQJJ2iEgRrpohlyBo3f4QK7Kl+EcAYZgAoNVmZWXK704YAa3FwBxgSGUOs5htvGRz4Sgj3yFkSJFBuv/sxu5yk998T8WDJzvv/2RX19HtTUW1S+wpKRKRjJ6zzz/1/OPdFdWGlAKbvzS4PHOtURikg9AGz0LbIB85S/cPOpoXvuue8/iV2H1vPTy3ddvOeZ37HGmO3OmSzVzR+NS53+84dHlFhXPLqtzSO+5ruHM2vXtBdxP87LOzKAD359j/INYIbyPabIi3Cq6Wa+SaGe78diIzu7qcblcAa6/fJRvNopXFJnO+U9KKM5bqH5LM0iQSVmpPCPDu7ZT4Aoubz3709EBTyrTDjyx8MQXgUH1nqm7TWng4TzE4i4AsKskBITXfSyC4Fkl5MxnJDiKSIDSJAsGvd1y+/eNDp2e+A+5d8HeiiunrTkT6TqWLIs+/QRoWr98s0qj8uuzLuS22Ytufg3rdTaHn1m46sfgGKHXt0MGnLaRHdnwN37tvHcWKo2V6lnPxL4UvUQcRdOzmZSQs8X5CH5OxXMXpkATuDz8Et0SH4uyCRR+TjmBDP1GvsVrWEGVzEj33YVQ9jAtIKpqsl/s/0xrocwAAeJxjYGRgYADig3cEzsTz23xl4GZnAIHLRucNkWl2BrA4BwMTiAIAF4IITwB4nGNgZGBgZwCChWASxGZkQAXyABOUANh4nGNnYGBgHyAMADa8ANoAAAAAAAAOAFAAZgCyAMYA5gEeAUgBdAGcAfICLgKOAroDCgOOA7AD6gQ4BHwEuAToBQwFogXoBjYGbAbaB3IHqHicY2BkYGCQZ8hlYGcAASYg5gJCBob/YD4DABbVAaoAeJxdkE1qg0AYhl8Tk9AIoVDaVSmzahcF87PMARLIMoFAl0ZHY1BHdBJIT9AT9AQ9RQ9Qeqy+yteNMzDzfM+88w0K4BY/cNAMB6N2bUaPPBLukybCLvleeAAPj8JD+hfhMV7hC3u4wxs7OO4NzQSZcI/8Ltwnfwi75E/hAR7wJTyk/xYeY49fYQ/PztM+jbTZ7LY6OWdBJdX/pqs6NYWa+zMxa13oKrA6Uoerqi/JwtpYxZXJ1coUVmeZUWVlTjq0/tHacjmdxuL90OR8O0UEDYMNdtiSEpz5XQGqzlm30kzUdAYFFOb8R7NOZk0q2lwAyz1i7oAr1xoXvrOgtYhZx8wY5KRV269JZ5yGpmzPTjQhvY9je6vEElPOuJP3mWKnP5M3V+YAAAB4nG2P2XLCMAxFfYFspGUp3Te+IB9lHJF4cOzUS2n/voaEGR6qB+lKo+WITdhga/a/bRnDBFPMkCBFhhwF5ihxg1sssMQKa9xhg3s84BFPeMYLXvGGd3zgE9tZr/hveXKVkFYoSnoeHJXfRoWOqi54mo9ameNFdrK+dLSyaVf7oJQTlkhXpD3Z5XXhR/rUfQVuKXO91Jps4cLOS6/I5YL3XhodRRsVWZe4NnZOhWnSAWgxhMoEr6SmzZieF43Mk7ZOBdeCVGrp9Eu+54J2xhySplfB5XHwQLXUmT9KH6+kPnQ7ZYuIEzNyfs1DLU1VU4SWZ6LkXGHsD1ZKbMw=) format("woff"), url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAAKAIAAAwAgT1MvMlGRXgUAAAEoAAAAVmNtYXDiMBC2AAAB/AAAAUpnbHlmW/HoBwAAA4gAAA9QaGVhZAyD6V8AAADQAAAANmhoZWEOogcgAAAArAAAACRobXR42QAAAAAAAYAAAAB8bG9jYTDINOoAAANIAAAAQG1heHABMAB5AAABCAAAACBuYW1l1cf1oAAAEtgAAAIKcG9zdGZEjeMAABTkAAABgQABAAAHAAAAAKEHAAAAAAAHAAABAAAAAAAAAAAAAAAAAAAAHwABAAAAAQAAwdxheF8PPPUACwcAAAAAANMyzzEAAAAA0zLPMQAAAAAHAAcAAAAACAACAAAAAAAAAAEAAAAfAG0ABwAAAAAAAgAAAAoACgAAAP8AAAAAAAAAAQcAAZAABQAIBHEE5gAAAPoEcQTmAAADXABXAc4AAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZABA8QHxHgcAAAAAoQcAAAAAAAABAAAAAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAAAAAMAAAADAAAAHAABAAAAAABEAAMAAQAAABwABAAoAAAABgAEAAEAAgAA8R7//wAAAADxAf//AAAPAAABAAAAAAAAAAABBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAFAAZgCyAMYA5gEeAUgBdAGcAfICLgKOAroDCgOOA7AD6gQ4BHwEuAToBQwFogXoBjYGbAbaB3IHqAABAAAAAAWLBYsAAgAAAREBAlUDNgWL++oCCwAAAwAAAAAGawZrAAIADgAaAAAJAhMEAAMSAAUkABMCAAEmACc2ADcWABcGAALrAcD+QJX+w/5aCAgBpgE9AT0BpggI/lr+w/3+rgYGAVL9/QFSBgb+rgIwAVABUAGbCP5a/sP+w/5aCAgBpgE9AT0BpvrIBgFS/f0BUgYG/q79/f6uAAAAAgAAAAAFQAWLAAMABwAAASERKQERIREBwAEr/tUCVQErAXUEFvvqBBYAAAAEAAAAAAYgBiAABgATACQAJwAAAS4BJxUXNjcGBxc+ATUmACcVFhIBBwEhESEBEQEGBxU+ATcXNwEHFwTQAWVVuAO7AidxJSgF/t/lpc77t18BYf6fASsBdQE+TF1OijuZX/1gnJwDgGSeK6W4GBhqW3FGnFT0AWM4mjT+9AHrX/6f/kD+iwH2/sI7HZoSRDGYXwSWnJwAAAEAAAAABKsF1gAFAAABESEBEQECCwEqAXb+igRg/kD+iwSq/osAAAACAAAAAAVmBdYABgAMAAABLgEnET4BAREhAREBBWUBZVRUZfwRASsBdf6LA4Bkniv9piueAUT+QP6LBKr+iwAAAwAAAAAGIAYPAAUADAAaAAATESEBEQEFLgEnET4BAxUWEhcGAgcVNgA3JgDgASsBdf6LAsUBZVVVZbqlzgMDzqXlASEFBf7fBGD+QP6LBKr+i+Bkniv9piueAvOaNP70tbX+9DSaOAFi9fUBYgAAAAQAAAAABYsFiwAFAAsAEQAXAAABIxEhNSMDMzUzNSEBIxUhESMDFTMVMxECC5YBduCWluD+igOA4AF2luDglgLr/oqWAgrglvyAlgF2AqCW4AF2AAQAAAAABYsFiwAFAAsAEQAXAAABMxUzESETIxUhESMBMzUzNSETNSMRITUBdeCW/org4AF2lgHAluD+ipaWAXYCVeABdgHAlgF2++rglgHA4P6KlgAAAAACAAAAAAXWBdYADwATAAABIQ4BBxEeARchPgE3ES4BAyERIQVA/IA/VQEBVT8DgD9VAQFVP/yAA4AF1QFVP/yAP1UBAVU/A4A/VfvsA4AAAAYAAAAABmsGawAHAAwAEwAbACAAKAAACQEmJw4BBwElLgEnAQUhATYSNyYFAQYCBxYXIQUeARcBMwEWFz4BNwECvgFkTlSH8GEBEgOONemh/u4C5f3QAXpcaAEB/BP+3VxoAQEOAjD95DXpoQESeP7dTlSH8GH+7gPwAmgSAQFYUP4nd6X2Pv4nS/1zZAEBk01NAfhk/v+TTUhLpfY+Adn+CBIBAVhQAdkAAAAFAAAAAAZrBdYADwATABcAGwAfAAABIQ4BBxEeARchPgE3ES4BASEVIQEhNSEFITUhNSE1IQXV+1ZAVAICVEAEqkBUAgJU+xYBKv7WAur9FgLqAcD+1gEq/RYC6gXVAVU//IA/VQEBVT8DgD9V/ayV/tWVlZWWlQADAAAAAAYgBdYADwAnAD8AAAEhDgEHER4BFyE+ATcRLgEBIzUjFTM1MxUUBgcjLgEnET4BNzMeARUFIzUjFTM1MxUOAQcjLgE1ETQ2NzMeARcFi/vqP1QCAlQ/BBY/VAICVP1rcJWVcCog4CAqAQEqIOAgKgILcJWVcAEqIOAgKiog4CAqAQXVAVU//IA/VQEBVT8DgD9V/fcl4CVKICoBASogASogKgEBKiBKJeAlSiAqAQEqIAEqICoBASogAAAGAAAAAAYgBPYAAwAHAAsADwATABcAABMzNSMRMzUjETM1IwEhNSERITUhERUhNeCVlZWVlZUBKwQV++sEFfvrBBUDNZb+QJUBwJX+QJb+QJUCVZWVAAAAAQAAAAAGIAZsAC4AAAEiBgcBNjQnAR4BMz4BNy4BJw4BBxQXAS4BIw4BBx4BFzI2NwEGBx4BFz4BNy4BBUArSh797AcHAg8eTixffwICf19ffwIH/fEeTixffwICf18sTh4CFAUBA3tcXHsDA3sCTx8bATcZNhkBNB0gAn9fX38CAn9fGxn+zRwgAn9fX38CIBz+yhcaXHsCAntcXXsAAAIAAAAABlkGawBDAE8AAAE2NCc3PgEnAy4BDwEmLwEuASchDgEPAQYHJyYGBwMGFh8BBhQXBw4BFxMeAT8BFh8BHgEXIT4BPwE2NxcWNjcTNiYnBS4BJz4BNx4BFw4BBasFBZ4KBgeWBxkNujpEHAMUD/7WDxQCHEU5ug0aB5UHBQudBQWdCwUHlQcaDbo5RRwCFA8BKg8UAhxFOboNGgeVBwUL/ThvlAIClG9vlAIClAM3JEokewkaDQEDDAkFSy0cxg4RAQERDsYcLUsFCQz+/QwbCXskSiR7CRoN/v0MCQVLLRzGDhEBAREOxhwtSwUJDAEDDBsJQQKUb2+UAgKUb2+UAAAAAAEAAAAABmsGawALAAATEgAFJAATAgAlBACVCAGmAT0BPQGmCAj+Wv7D/sP+WgOA/sP+WggIAaYBPQE9AaYICP5aAAAAAgAAAAAGawZrAAsAFwAAAQQAAxIABSQAEwIAASYAJzYANxYAFwYAA4D+w/5aCAgBpgE9AT0BpggI/lr+w/3+rgYGAVL9/QFSBgb+rgZrCP5a/sP+w/5aCAgBpgE9AT0BpvrIBgFS/f0BUgYG/q79/f6uAAADAAAAAAZrBmsACwAXACMAAAEEAAMSAAUkABMCAAEmACc2ADcWABcGAAMOAQcuASc+ATceAQOA/sP+WggIAaYBPQE9AaYICP5a/sP9/q4GBgFS/f0BUgYG/q4dAn9fX38CAn9fX38Gawj+Wv7D/sP+WggIAaYBPQE9Aab6yAYBUv39AVIGBv6u/f3+rgJPX38CAn9fX38CAn8AAAAEAAAAAAYgBiAADwAbACUAKQAAASEOAQcRHgEXIT4BNxEuAQEjNSMVIxEzFTM1OwEhHgEXEQ4BByE3MzUjBYv76j9UAgJUPwQWP1QCAlT9a3CVcHCVcJYBKiAqAQEqIP7WcJWVBiACVD/76j9UAgJUPwQWP1T8gpWVAcC7uwEqIP7WICoBcOAAAgAAAAAGawZrAAsAFwAAAQQAAxIABSQAEwIAEwcJAScJATcJARcBA4D+w/5aCAgBpgE9AT0BpggI/lo4af70/vRpAQv+9WkBDAEMaf71BmsI/lr+w/7D/loICAGmAT0BPQGm/BFpAQv+9WkBDAEMaf71AQtp/vQAAQAAAAAF1ga2ABYAAAERCQERHgEXDgEHLgEnIxYAFzYANyYAA4D+iwF1vv0FBf2+vv0FlQYBUf7+AVEGBv6vBYsBKv6L/osBKgT9v779BQX9vv7+rwYGAVH+/gFRAAAAAQAAAAAFPwcAABQAAAERIyIGHQEhAyMRIREjETM1NDYzMgU/nVY8ASUn/v7O///QrZMG9P74SEi9/tj9CQL3ASjaus0AAAAABAAAAAAGjgcAADAARQBgAGwAAAEUHgMVFAcGBCMiJicmNTQ2NzYlLgE1NDcGIyImNTQ2Nz4BMyEHIx4BFRQOAycyNjc2NTQuAiMiBgcGFRQeAxMyPgI1NC4BLwEmLwImIyIOAxUUHgIBMxUjFSM1IzUzNTMDH0BbWkAwSP7qn4TlOSVZSoMBESAfFS4WlMtIP03TcAGiioNKTDFFRjGSJlAaNSI/akAqURkvFCs9WTY6a1s3Dg8THgocJU4QIDVob1M2RnF9A2vV1WnU1GkD5CRFQ1CATlpTenNTYDxHUYouUhIqQCkkMQTBlFKaNkJAWD+MWkhzRztAPiEbOWY6hn1SJyE7ZS5nZ1I0/JcaNF4+GTAkGCMLFx04Ag4kOF07Rms7HQNsbNvbbNkAAwAAAAAGgAZsAAMADgAqAAABESERARYGKwEiJjQ2MhYBESERNCYjIgYHBhURIRIQLwEhFSM+AzMyFgHd/rYBXwFnVAJSZGemZASP/rdRVj9VFQv+twIBAQFJAhQqR2c/q9AEj/whA98BMkliYpNhYfzd/cgCEml3RTMeM/3XAY8B8DAwkCAwOB/jAAABAAAAAAaUBgAAMQAAAQYHFhUUAg4BBCMgJxYzMjcuAScWMzI3LgE9ARYXLgE1NDcWBBcmNTQ2MzIXNjcGBzYGlENfAUyb1v7SrP7x4SMr4bBpph8hHCsqcJNETkJOLHkBW8YIvYaMYG1gJWldBWhiRQ4cgv797rdtkQSKAn1hBQsXsXUEJgMsjlNYS5WzCiYkhr1mFTlzPwoAAAABAAAAAAWABwAAIgAAARcOAQcGLgM1ESM1PgQ3PgE7AREhFSERFB4CNzYFMFAXsFlorXBOIahIckQwFAUBBwT0AU3+sg0gQzBOAc/tIz4BAjhceHg6AiDXGlddb1ctBQf+WPz9+h40NR4BAgABAAAAAAaABoAASgAAARQCBCMiJzY/AR4BMzI+ATU0LgEjIg4DFRQWFxY/ATY3NicmNTQ2MzIWFRQGIyImNz4CNTQmIyIGFRQXAwYXJgI1NBIkIAQSBoDO/p/Rb2s7EzYUaj15vmh34o5ptn9bK1BNHggIBgIGETPRqZepiWs9Sg4IJRc2Mj5WGWMRBM7+zgFhAaIBYc4DgNH+n84gXUfTJzmJ8JZyyH46YH2GQ2ieIAwgHxgGFxQ9WpfZpIOq7lc9I3VZHzJCclVJMf5eRmtbAXzp0QFhzs7+nwAABwAAAAAHAATPAA4AFwAqAD0AUABaAF0AAAERNh4CBw4BBwYmIycmNxY2NzYmBxEUBRY2Nz4BNy4BJyMGHwEeARcOARcWNjc+ATcuAScjBh8BHgEXFAYXFjY3PgE3LgEnIwYfAR4BFw4BBTM/ARUzESMGAyUVJwMchM2UWwgNq4JHrQgBAapUaAoJcWMBfiIhDiMrAQJLMB0BBAokNAIBPmMiIQ4iLAECSzAeAQUKJDQBP2MiIQ4iLAECSzAeAQUKJDQBAT75g+5B4arNLNIBJ44ByQL9BQ9mvYCKwA8FBQMDwwJVTGdzBf6VB8IHNR08lld9uT4LCRA/qGNxvUwHNR08lld9uT4LCRA/qGNxvUwHNR08lld9uT4LCRA/qGNxvVJkAWUDDEf+tYP5AQAAAAEAAAAABiAGtgAbAAABBAADER4BFzMRITU2ADcWABcVIREzPgE3EQIAA4D+4v6FBwJ/X+D+1QYBJ97eAScG/tXgX38CB/6FBrUH/oX+4v32X38CAlWV3gEnBgb+2d6V/asCf18CCgEeAXsAAAAAEADGAAEAAAAAAAEABwAAAAEAAAAAAAIABwAHAAEAAAAAAAMABwAOAAEAAAAAAAQABwAVAAEAAAAAAAUACwAcAAEAAAAAAAYABwAnAAEAAAAAAAoAKwAuAAEAAAAAAAsAEwBZAAMAAQQJAAEADgBsAAMAAQQJAAIADgB6AAMAAQQJAAMADgCIAAMAAQQJAAQADgCWAAMAAQQJAAUAFgCkAAMAAQQJAAYADgC6AAMAAQQJAAoAVgDIAAMAAQQJAAsAJgEeVmlkZW9KU1JlZ3VsYXJWaWRlb0pTVmlkZW9KU1ZlcnNpb24gMS4wVmlkZW9KU0dlbmVyYXRlZCBieSBzdmcydHRmIGZyb20gRm9udGVsbG8gcHJvamVjdC5odHRwOi8vZm9udGVsbG8uY29tAFYAaQBkAGUAbwBKAFMAUgBlAGcAdQBsAGEAcgBWAGkAZABlAG8ASgBTAFYAaQBkAGUAbwBKAFMAVgBlAHIAcwBpAG8AbgAgADEALgAwAFYAaQBkAGUAbwBKAFMARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABzAHYAZwAyAHQAdABmACAAZgByAG8AbQAgAEYAbwBuAHQAZQBsAGwAbwAgAHAAcgBvAGoAZQBjAHQALgBoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAAAAAgAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAABAgEDAQQBBQEGAQcBCAEJAQoBCwEMAQ0BDgEPARABEQESARMBFAEVARYBFwEYARkBGgEbARwBHQEeAR8EcGxheQtwbGF5LWNpcmNsZQVwYXVzZQt2b2x1bWUtbXV0ZQp2b2x1bWUtbG93CnZvbHVtZS1taWQLdm9sdW1lLWhpZ2gQZnVsbHNjcmVlbi1lbnRlcg9mdWxsc2NyZWVuLWV4aXQGc3F1YXJlB3NwaW5uZXIJc3VidGl0bGVzCGNhcHRpb25zCGNoYXB0ZXJzBXNoYXJlA2NvZwZjaXJjbGUOY2lyY2xlLW91dGxpbmUTY2lyY2xlLWlubmVyLWNpcmNsZQJoZAZjYW5jZWwGcmVwbGF5CGZhY2Vib29rBWdwbHVzCGxpbmtlZGluB3R3aXR0ZXIGdHVtYmxyCXBpbnRlcmVzdBFhdWRpby1kZXNjcmlwdGlvbgVhdWRpbwAAAAAA) format("truetype"); + src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAABBIAAsAAAAAGoQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADsAAABUIIslek9TLzIAAAFEAAAAPgAAAFZRiV3RY21hcAAAAYQAAADQAAADIjn098ZnbHlmAAACVAAACv4AABEIAwnSw2hlYWQAAA1UAAAAKwAAADYSy2hLaGhlYQAADYAAAAAbAAAAJA4DByFobXR4AAANnAAAAA8AAACE4AAAAGxvY2EAAA2sAAAARAAAAEQ9NEHGbWF4cAAADfAAAAAfAAAAIAEyAIFuYW1lAAAOEAAAASUAAAIK1cf1oHBvc3QAAA84AAABDwAAAZ5AAl/0eJxjYGRgYOBiMGCwY2BycfMJYeDLSSzJY5BiYGGAAJA8MpsxJzM9kYEDxgPKsYBpDiBmg4gCACY7BUgAeJxjYGQ7xTiBgZWBgaWQ5RkDA8MvCM0cwxDOeI6BgYmBlZkBKwhIc01hcPjI+FGBHcRdyA4RZgQRAC4HCwEAAHic7dFprsIgAEXhg8U61XmeWcBb1FuQP4w7ZQXK5boMm3yclFDSANAHmuKviBBeBPQ8ymyo8w3jOh/5r2ui5nN6v8sYNJb3WMdeWRvLji0DhozKdxM6psyYs2DJijUbtuzYc+DIiTMXrty4k8oGLb+n0xCe37ekM7Z66j1DbUy3l6PpHnLfdLO5NdSBoQ4NdWSoY9ON54mhdqa/y1NDnRnq3FAXhro01JWhrg11Y6hbQ90Z6t5QD4Z6NNSToZ4N9WKoV0O9GerdUJORPqkhTd54nJ1YDXBU1RV+576/JBs2bPYPkrDZt5vsJrv53V/I5mclhGDCTwgGBQQSTEji4hCkYIAGd4TGIWFAhV0RQTpWmQp1xv6hA4OTOlNr2zFANbHUYbq2OtNCpViRqsk+e+7bTQAhzti8vPfuPffcc88959zznbcMMPjHD/KDDGEY0ABpYX384NhlomIYlo4JISGEY9mMh2FSidYiqkEUphtNYDSY/dXg9023l4DdxlqUl0chuZRhncJKrsCQHIwcGuwfnhMIzBnuH4Sym+1D2zaGjheXlhYfD238z80mKYMmvJ5XeOTzd8z9eujbMxJNhu4C9xPE/bCMiDuSNIWgkTQwBE55hLSAE7ZwhrHLnAHZOGV/kmBGTiNjZxzI77Hb7Hqjz68TjT6vh+5JT/cCIkqS0D6CqPf5jX4Qjdx5j6vlDfZM4aZFdbVXIxtOlJaP/WottMnH6CJQ3bTiue3PrY23HjnChtuamxwvvzFjxkPrNj3z0tG9T561HDYf6OgmRWvlY3JQHoQb8ltV2Yet7YfWctEjR1AtxS/cSX6U4alf6NJEBQ7YKg9wrXQKd0IeZCb2ux75Uhh1Un+Nz+9LTOE7PK777nN5xqdTneTBhCbx446mZrhnUkrCz2YhA9dSMxaG0SYmT8hi9ZPu1E94PJYQSH6LRmhxec7Q7ZeXntgQuVpbh+a4qWNsckVyTdn0P7o7DpgPW84+uRcq0BITflBikGdUjAZ9wYBVI3mtrNvr9kpg1UsaK6t3690aoorC1lg0GpMH2HAMtkZjsSi5Ig9ESVosOh7GQfLjKNLvKpMKkLSKNFAka710GdgSi8oDMSoNhqjkKBXTgn3swtaxyzGkUzIzae9RtLdWkSlZ1KDX6EzgllzV4NV4SoDFSOGD4+HCeQUF8wrZ5Hs8zIb5EaVxy8DYFTbMCJPnLIWZxugZE2NlivC0gc1qEQUR8jEKgZcAXeH18BiCgl5nlHh0CrjB4Hb5fX4gb0J7c9PuHVsfgkx2n/vTY/JV8kn8PGxf7faOZ8qX8JVByuIf4whk9sqXli2hvPJV9hrp0hY7l8r2x37ydaVsb4xvXv/47v2NjfCl8m5oRDJclFMoE1yk0Uh1Te4/m8lFXe9qBZD0EkheicebXvzI2PLCuoKCukLuhPIeKwaHPEouxw3kMqaIUXDQ1p0mip+MyCORSCQaoUsnY1VZ38nUTrG21WvVo4f1OsEJFhvSfAFwGfT8VHRMeAVUpwLOoLzjT/REIj3O3FhuURE+nERF+0pTId5Fyxv5sfwGyg4O+my4vZv0sZm7oeQlFZORiB+tG0MweVNraeitl7yxiPIHTk4/diVxs94o5lEYishB2iAtkchEnsActoEpx44Fo8XnsQMaA22BlqC20RmhBKzYojZyYaxg+JggMc4HHY2m+L9EkWSYljirOisrO7d3VorxzyZ6Vc4lJqITAu1b2wOBdrLElAP+bFc2eGaZFVbkmJktv5uT6Jlz5D/MnBFor6ig/JPnRViBsV3LNKGGqB1ChJ0tgQywlVLFJIuQgTFttwkiKxhyQdAZMdMYtSaoAewqfvXVYPAbDT6/1mez85YS8FSDywQ6NfAnef6FNEGMilnppyvn5rB6tTyq1pOceRWnp2WJEZFXHeX5oyoem1nTTgdqc4heDY7bOeKz63vnz+/dRx+s31Ht2JGanQ5seirfWJL9tjozU/12TnEjn5oux9OzU3ckGbBzBwNOyk69JykKH0n/0LM9A72tuwM3zQpIRu4AxiToseEpgPOmbROyFe9/X2yeUvoUsCyEvjcgs7fpWP3/aKlFN0+6HFUe6D9HFz/XPwBlN9tTqNyZjFJ8UO2RUT5/h4CptCctEyeisnOyXjALEp7dXKaQKf6O7IMnGjNNACRMLxqdYJX8eMLvmmd68D+ayBLyKKYZwYxDt/GNhzETDJ05Qxlyi3pi3/Z93ndYVSumgj0V/KkIFlO6+1K3fF2+3g0q+YtuSIf0bvmLqV09nnobI6hwcjIP8aPCKayjsF5JBY3LaKAeRLSyYB1h81oTwe9SlPMkXB7G0mfL9q71gaqqwPqu67QRKS1+ObTx+sbQy9QV2OQHEScGkdFBeT7v7qisqqrs6N52i78/R+6S0qQONVj26agOVoswCyQWIV5D86vH53bxNUeXV0K+XZaHv/nm/KsHhOvylwsWnJX/HE8l/4WCv5x+l5n08z6UU8bUMa3MBpSmM7F63AxntdC9eBCKEZW9Hr+ABNqtxgAQrSbMtmrW7lKQuoSgBhSrTazWVU2QAKWY8wiiuhqFmQgWJBgoXiuWIm42N7hqZbBsgXz52O5P5uSvaNgFGnOuvsRw8I8Laha91wMvDuxqWFheN7/8GVtTltdS83DQsXRmqc5ZtcJXEVrlV2doTWk5+Yunm71dG5f55m/qY0MjI93vv9/NfpxXV9sUXrxy2fbNy1or65cOlDRnOoKFeeXcbw42H/bNDT5Qs3flgs31gWC1lD1nfUV/X7NdCnSUdHY2e8afzfKsqZ5ZljfDqjLOmk3UebNXB+aHArPYDRs+/HDDxeT5DiP+sFg7OpRaVQMGBV89PpeBdj22hCE0Uub0UqwLrNWsG0cuyadgLXTeR5rbO4+3c/vl15cur2nRq+TXCQDcS3SO+s6ak+e5/eMS+1dw3btu3YG2tvFL8XdIZvdjdW6TO/4B7IdrZWVPmctm5/59AgsPItTSbCiIBr2OqIGzmu20SMKAS7yqwGBUfGfgjDYlLLDeF0SfcLB2LSx8flT+08/kzz6yOj96rft4rpTjdPQcmLd47uKibbDq7ZSz/XtbH2nN717Nd62rU+c8Icevvv7I09wA6WvjVcafb+FsbNG+ZQ80Rn6ZZsvrP7teP2dzTdoETvNhjCmsr8FID2sJ69VYvdUcxk4AzYRlKcaE38eXNRlfW9H1as9i6acLHp1XpuNB5K7DIvkX08y1ZYvh3KfWaiCzH+ztrSDmD7LuX73x/mJelB8Yj39t8nhNQJJ2CAthpoFGLsGgtSOCJooCGoaJAMTjSWHVZ08YAa1Fg9lPI5U6DOsGVjDasJeZZ+YyhfCwfOzCxlBA69M9XLXtza7H/rav+9Tjq5xNi0wpKQIRNO4Lrzz7yp5QVYM6Jd/oc1Uvn/mQhhuWh6ENXoS2YTZ8QT42bF5d/559zp5r0Uff2VnR2tdf2/WCOd2cO0Mw6qpWPnvxpV0nrt5fZd2yItc199GWe8vlNfNDq+CH/7yAAnB9hn7T4QO4c1g9ScxsZgmzntnE/IDGndtHMw69lFwoCnYsMGx+rBp8JSBqdLzBr9QRPq/PbhWMWFtQZp1xguy/haw3TEHm3TWAnxFWQQWgt7M5OV0lCz1VRYucpWliy7z6Zd4urwPIyeZQqli2Lgg7szJV09PysATbOQtYIrB2YzbkJYkGgJ0m4AjPUap1pvYu1K9qr97z0Yl3p332b2LYB78ncYIlRkau/8GObSsOlZancACE5d5ily+c2+7h5Yj4lqhVmXXB+iXLfvdqSgqfKtQvfHDV0OnvQR1qhw42XS/vkvsh/hXcrDFP0a+SJNIomEfD1nsrYGO+1bgTOJhM8Hv6ek+7vVglxuSRwoKn17S937bm6YJCeSSG0Op1n+7tE37tcZ/p7dsTv4EUrGpDbWueKigsLHhqTVsoEj+JU0kaSjnj9tz8/gryQWwJ9BcJXBC/7smO+I/IFURJetFPrdt5WcoL6DbEJaygI8CTHfQTjf40ofD+DwalTqIAAHicY2BkYGAA4jC5t2/j+W2+MnCzM4DAtTC+5cg0OyNYnIOBCUQBAAceB90AeJxjYGRgYGcAARD5/z87IwMjAypQBAAtgwI4AHicY2BgYGAfYAwAOkQA4QAAAAAAAA4AaAB+AMwA4AECAUIBbAGYAcICGAJYArQC4AMwA7AD3gQwBJYE3AUkBWYFigYgBmYGtAbqB1gIEghYCG4IhHicY2BkYGBQZChlYGcAASYg5gJCBob/YD4DABfTAbQAeJxdkE1qg0AYhl8Tk9AIoVDaVSmzahcF87PMARLIMoFAl0ZHY1BHdBJIT9AT9AQ9RQ9Qeqy+yteNMzDzfM+88w0K4BY/cNAMB6N2bUaPPBLukybCLvleeAAPj8JD+hfhMV7hC3u4wxs7OO4NzQSZcI/8Ltwnfwi75E/hAR7wJTyk/xYeY49fYQ/PztM+jbTZ7LY6OWdBJdX/pqs6NYWa+zMxa13oKrA6Uoerqi/JwtpYxZXJ1coUVmeZUWVlTjq0/tHacjmdxuL90OR8O0UEDYMNdtiSEpz5XQGqzlm30kzUdAYFFOb8R7NOZk0q2lwAyz1i7oAr1xoXvrOgtYhZx8wY5KRV269JZ5yGpmzPTjQhvY9je6vEElPOuJP3mWKnP5M3V+YAAAB4nG2PyXLCMBBE3YCNDWEL2ffk7o8S8oCnkCVHC5C/jzBQlUP6IHVPzYyekl5y0iL5X5/ooY8BUmQYIkeBEca4wgRTzDDHAtdY4ga3uMM9HvCIJzzjBa94wzs+8ImvZNAq8TM+HqVkKxWlrQiOxjujQkNlEzyNzl6Z/cU2XF06at7U83VQyklLpEvSnuzsb+HAPnPfQVgaupa1Jlu4sPLsFblcitaz0dHU0ZF1qatjZ1+aTXYCmp6u0gSvWNPyHLtFZ+ZeXWVSaEkqs3T8S74WklbGbNNNq4LL4+CWKtZDv2cfX8l8aFbKFhEnJnJ+IULFpqwoQnNHlHaVQtPBl+ypmbSWdmyC61KS/AKZC3Y+AA==) format("woff"), url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwR1NVQiCLJXoAAAE4AAAAVE9TLzJRiV3RAAABjAAAAFZjbWFwOfT3xgAAAmgAAAMiZ2x5ZgMJ0sMAAAXQAAARCGhlYWQSy2hLAAAA4AAAADZoaGVhDgMHIQAAALwAAAAkaG10eOAAAAAAAAHkAAAAhGxvY2E9NEHGAAAFjAAAAERtYXhwATIAgQAAARgAAAAgbmFtZdXH9aAAABbYAAACCnBvc3RAAl/0AAAY5AAAAZ4AAQAABwAAAAAABwAAAP//BwEAAQAAAAAAAAAAAAAAAAAAACEAAQAAAAEAAFYfTwlfDzz1AAsHAAAAAADWVg6nAAAAANZWDqcAAAAABwEHAAAAAAgAAgAAAAAAAAABAAAAIQB1AAcAAAAAAAIAAAAKAAoAAAD/AAAAAAAAAAEAAAAKADAAPgACREZMVAAObGF0bgAaAAQAAAAAAAAAAQAAAAQAAAAAAAAAAQAAAAFsaWdhAAgAAAABAAAAAQAEAAQAAAABAAgAAQAGAAAAAQAAAAEGygGQAAUAAARxBOYAAAD6BHEE5gAAA1wAVwHOAAACAAUDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBmRWQAQPEB8SAHAAAAAKEHAAAAAAAAAQAAAAAAAAAAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAAAAAUAAAADAAAALAAAAAQAAAGSAAEAAAAAAIwAAwABAAAALAADAAoAAAGSAAQAYAAAAAQABAABAADxIP//AADxAf//AAAAAQAEAAAAAQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgAAABBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAGQAAAAAAAAACAAAPEBAADxAQAAAAEAAPECAADxAgAAAAIAAPEDAADxAwAAAAMAAPEEAADxBAAAAAQAAPEFAADxBQAAAAUAAPEGAADxBgAAAAYAAPEHAADxBwAAAAcAAPEIAADxCAAAAAgAAPEJAADxCQAAAAkAAPEKAADxCgAAAAoAAPELAADxCwAAAAsAAPEMAADxDAAAAAwAAPENAADxDQAAAA0AAPEOAADxDgAAAA4AAPEPAADxDwAAAA8AAPEQAADxEAAAABAAAPERAADxEQAAABEAAPESAADxEgAAABIAAPETAADxEwAAABMAAPEUAADxFAAAABQAAPEVAADxFQAAABUAAPEWAADxFgAAABYAAPEXAADxFwAAABcAAPEYAADxGAAAABgAAPEZAADxGQAAABkAAPEaAADxGgAAABoAAPEbAADxGwAAABsAAPEcAADxHAAAABwAAPEdAADxHQAAAB0AAPEeAADxHgAAAB4AAPEfAADxHwAAAB8AAPEgAADxIAAAACAAAAAAAAAADgBoAH4AzADgAQIBQgFsAZgBwgIYAlgCtALgAzADsAPeBDAElgTcBSQFZgWKBiAGZga0BuoHWAgSCFgIbgiEAAEAAAAABYsFiwACAAABEQECVQM2BYv76gILAAADAAAAAAZrBmsAAgAbADQAAAkCEyIHDgEHBhAXHgEXFiA3PgE3NhAnLgEnJgMiJy4BJyY0Nz4BNzYyFx4BFxYUBw4BBwYC6wHA/kCVmIuGzjk7OznOhosBMIuGzjk7OznOhouYeW9rpi0vLy2ma2/yb2umLS8vLaZrbwIwAVABUAGbOznOhov+0IuGzjk7OznOhosBMIuGzjk7+sAvLaZrb/Jva6YtLy8tpmtv8m9rpi0vAAACAAAAAAVABYsAAwAHAAABIREpAREhEQHAASv+1QJVASsBdQQW++oEFgAAAAQAAAAABiEGIAAHABcAJwAqAAABNCcmJxUXNjcUBxc2NTQnLgEnFR4BFxYBBwEhESEBEQEGBxU2Nxc3AQcXBNA0MlW4A7spcU1FQ+6VbKovMfu0XwFh/p8BKwF1AT5QWZl6mV/9YJycA4BhUlAqpbgYGGNicZKknYyHvSKaIJNlaQIsX/6f/kD+iwH2/sI9G5ojZJhfBJacnAAAAAEAAAAABKsF1gAFAAABESEBEQECCwEqAXb+igRg/kD+iwSq/osAAAACAAAAAAVmBdYACAAOAAABNCcmJxE2NzYBESEBEQEFZTQyVFQyNPwQASsBdf6LA4BhUlAq/aYqUFIBQf5A/osEqv6LAAMAAAAABiAGDwAFAA4AIgAAExEhAREBBTQnJicRNjc2AxUeARcWFAcOAQcVPgE3NhAnLgHgASsBdf6LAsU0MlVVMjS7bKovMTEvqmyV7kNFRUPuBGD+QP6LBKr+i+BhUlAq/aYqUFIC8Jogk2Vp6GllkyCaIr2HjAE6jIe9AAAABAAAAAAFiwWLAAUACwARABcAAAEjESE1IwMzNTM1IQEjFSERIwMVMxUzEQILlgF24JaW4P6KA4DgAXaW4OCWAuv+ipYCCuCW/ICWAXYCoJbgAXYABAAAAAAFiwWLAAUACwARABcAAAEzFTMRIRMjFSERIwEzNTM1IRM1IxEhNQF14Jb+iuDgAXaWAcCW4P6KlpYBdgJV4AF2AcCWAXb76uCWAcDg/oqWAAAAAAIAAAAABdYF1gATABcAAAEhIg4BFREUHgEzITI+ATURNC4BAyERIQVA/IApRCgoRCkDgClEKChEKfyAA4AF1ShEKfyAKUQoKEQpA4ApRCj76wOAAAYAAAAABmsGawAIAA0AFQAeACMALAAACQEmIyIHBgcBJS4BJwEFIQE2NzY1NAUBBgcGFRQXIQUeARcBMwEWMzI3NjcBAr4BZFJQhHt2YwESA44z7Z/+7gLl/dABel0zNfwS/t1dMzUPAjD95DPtnwESeP7dU0+Ee3Zj/u4D8AJoEy0rUf4nd6P6PP4nS/1zZn+Ej0tLAfhmf4SPS0pLo/o8Adn+CBMtK1EB2QAFAAAAAAZrBdYAEwAXABsAHwAjAAABISIOARURFB4BMyEyPgE1ETQuAQEhFSEBITUhBSE1ITUhNSEF1ftWKUUoKEUpBKopRSgoRfstASr+1gLq/RYC6gHA/tYBKv0WAuoF1ShEKfyAKUQoKEQpA4ApRCj9q5X+1ZWVlZaVAAAAAAMAAAAABiAF1gATACsAQwAAASEiDgEVERQeATMhMj4BNRE0LgEBIzUjFTM1MxUUBisBIiY1ETQ2OwEyFhUFIzUjFTM1MxUUBisBIiY1ETQ2OwEyFhUFi/vqKEUoKEUoBBYoRSgoRf2CcJWVcCsf4B8sLB/gHysCC3CVlXAsH+AfKysf4B8sBdUoRCn8gClEKChEKQOAKUQo/fYl4CVKHywsHwEqHywsH0ol4CVKHywsHwEqHywsHwAGAAAAAAYgBPYAAwAHAAsADwATABcAABMzNSMRMzUjETM1IwEhNSERITUhERUhNeCVlZWVlZUBKwQV++sEFfvrBBUDNZb+QJUBwJX+QJb+QJUCVZWVAAAAAQAAAAAGIQZsADEAAAEiBgcBNjQnAR4BMzI+ATQuASIOARUUFwEuASMiDgEUHgEzMjY3AQYVFB4BMj4BNC4BBUAqSx797AcHAg8eTys9Zzw8Z3pnPAf98R5PKz1nPDxnPStPHgIUBjtkdmQ7O2QCTx4cATcbMhsBNB0gPGd6Zzw8Zz0ZG/7NHCA8Z3pnPCAc/soZGDtkOjpkdmQ7AAAAAAIAAAAABlkGawBDAFAAAAE2NCc3PgEnAy4BDwEmLwEuASMhIgYPAQYHJyYGBwMGFh8BBhQXBw4BFxMeAT8BFh8BHgEzITI2PwE2NxcWNjcTNiYnBSIuATQ+ATIeARQOAQWrBQWeCgYHlgcaDLo8QhwDFQ7+1g4VAhxEOroNGgeVBwULnQUFnQsFB5UHGg26O0McAhUOASoOFQIcRDq6DRoHlQcFC/04R3hGRniOeEZGeAM3Kj4qewkbDAEDDAkFSy4bxg4SEg7GHC1LBQkM/v0MGwl7Kj4qewkbDP79DAkFSy4bxg4SEg7GHC1LBQkMAQMMGwlBRniOeEZGeI54RgABAAAAAAZrBmsAGAAAExQXHgEXFiA3PgE3NhAnLgEnJiAHDgEHBpU7Oc6GiwEwi4bOOTs7Oc6Gi/7Qi4bOOTsDgJiLhs45Ozs5zoaLATCLhs45Ozs5zoaLAAAAAAIAAAAABmsGawAYADEAAAEiBw4BBwYQFx4BFxYgNz4BNzYQJy4BJyYDIicuAScmNDc+ATc2MhceARcWFAcOAQcGA4CYi4bOOTs7Oc6GiwEwi4bOOTs7Oc6Gi5h5b2umLS8vLaZrb/Jva6YtLy8tpmtvBms7Oc6Gi/7Qi4bOOTs7Oc6GiwEwi4bOOTv6wC8tpmtv8m9rpi0vLy2ma2/yb2umLS8AAwAAAAAGawZrABgAMQA+AAABIgcOAQcGEBceARcWIDc+ATc2ECcuAScmAyInLgEnJjQ3PgE3NjIXHgEXFhQHDgEHBhMUDgEiLgE0PgEyHgEDgJiKhs85Ozs5z4aKATCKhs85Ozs5z4aKmHlva6YtLy8tpmtv8m9rpi0vLy2ma29nPGd6Zzw8Z3pnPAZrOznPhor+0IqGzzk7OznPhooBMIqGzzk7+sAvLaZrb/Jva6YtLy8tpmtv8m9rpi0vAlU9Zzw8Z3pnPDxnAAAABAAAAAAGIAYhABMAHwApAC0AAAEhIg4BFREUHgEzITI+ATURNC4BASM1IxUjETMVMzU7ASEyFhURFAYjITczNSMFi/vqKEUoKEUoBBYoRSgoRf2CcJVwcJVwlgEqHywsH/7WcJWVBiAoRSj76ihFKChFKAQWKEUo/ICVlQHAu7ssH/7WHyxw4AAAAAACAAAAAAZrBmsAGAAkAAABIgcOAQcGEBceARcWIDc+ATc2ECcuAScmEwcJAScJATcJARcBA4CYi4bOOTs7Oc6GiwEwi4bOOTs7Oc6Gi91p/vT+9GkBC/71aQEMAQxp/vUGazs5zoaL/tCLhs45Ozs5zoaLATCLhs45O/wJaQEL/vVpAQwBDGn+9QELaf70AAABAAAAAAXWBrYAJwAAAREJAREyFxYXFhQHBgcGIicmJyY1IxQXHgEXFjI3PgE3NjQnLgEnJgOA/osBdXpoZjs9PTtmaPRoZjs9lS8tpWtv9G9rpS0vLy2la28FiwEq/ov+iwEqPTtmaPNpZTw9PTxlaXl5b2umLS8vLaZrb/Nva6UuLwABAAAAAAU/BwAAFAAAAREjIgYdASEDIxEhESMRMzU0NjMyBT+dVjwBJSf+/s7//9Ctkwb0/vhISL3+2P0JAvcBKNq6zQAAAAAEAAAAAAaOBwAAMABFAGAAbAAAARQeAxUUBwYEIyImJyY1NDY3NiUuATU0NwYjIiY1NDY3PgEzIQcjHgEVFA4DJzI2NzY1NC4CIyIGBwYVFB4DEzI+AjU0LgEvASYvAiYjIg4DFRQeAgEzFSMVIzUjNTM1MwMfQFtaQDBI/uqfhOU5JVlKgwERIB8VLhaUy0g/TdNwAaKKg0pMMUVGMZImUBo1Ij9qQCpRGS8UKz1ZNjprWzcODxMeChwlThAgNWhvUzZGcX0Da9XVadTUaQPkJEVDUIBOWlN6c1NgPEdRii5SEipAKSQxBMGUUpo2QkBYP4xaSHNHO0A+IRs5ZjqGfVInITtlLmdnUjT8lxo0Xj4ZMCQYIwsXHTgCDiQ4XTtGazsdA2xs29ts2QADAAAAAAaABmwAAwAOACoAAAERIREBFgYrASImNDYyFgERIRE0JiMiBgcGFREhEhAvASEVIz4DMzIWAd3+tgFfAWdUAlJkZ6ZkBI/+t1FWP1UVC/63AgEBAUkCFCpHZz+r0ASP/CED3wEySWJik2Fh/N39yAISaXdFMx4z/dcBjwHwMDCQIDA4H+MAAAEAAAAABpQGAAAxAAABBgcWFRQCDgEEIyAnFjMyNy4BJxYzMjcuAT0BFhcuATU0NxYEFyY1NDYzMhc2NwYHNgaUQ18BTJvW/tKs/vHhIyvhsGmmHyEcKypwk0ROQk4seQFbxgi9hoxgbWAlaV0FaGJFDhyC/v3ut22RBIoCfWEFCxexdQQmAyyOU1hLlbMKJiSGvWYVOXM/CgAAAAEAAAAABYAHAAAiAAABFw4BBwYuAzURIzU+BDc+ATsBESEVIREUHgI3NgUwUBewWWitcE4hqEhyRDAUBQEHBPQBTf6yDSBDME4Bz+0jPgECOFx4eDoCINcaV11vVy0FB/5Y/P36HjQ1HgECAAEAAAAABoAGgABKAAABFAIEIyInNj8BHgEzMj4BNTQuASMiDgMVFBYXFj8BNjc2JyY1NDYzMhYVFAYjIiY3PgI1NCYjIgYVFBcDBhcmAjU0EiQgBBIGgM7+n9FvazsTNhRqPXm+aHfijmm2f1srUE0eCAgGAgYRM9Gpl6mJaz1KDgglFzYyPlYZYxEEzv7OAWEBogFhzgOA0f6fziBdR9MnOYnwlnLIfjpgfYZDaJ4gDCAfGAYXFD1al9mkg6ruVz0jdVkfMkJyVUkx/l5Ga1sBfOnRAWHOzv6fAAAHAAAAAAcBBM8AFwAhADgATwBmAHEAdAAAAREzNhcWFxYXFhcWBw4BBwYHBicmLwEmNxY2NzYuAQcRFAUWNzY/ATY3NjU2JyMGFxYfARYXFhcUFxY3Nj8BNjc2NzYnIwYXFh8BFhcWFRYXFjc2PwE2NzY3NicjBhcWHwEWFxYVFgUzPwEVMxEjBgsBARUnAxwcaC5MND0sTSsvCgdVREdTNWg1KgECq1JrCQcwYkABfhoSCxAKJBQXAX4dAQMCBgMnFxsBJBoSCxAKJBQWAQF+HgEEAgUEJxcbASMZEwsQCiQUFgEBfh4BBAIFBCcXGwH5Q+5B4arNDfHvAhaOAckC/QIBAwwPHzdcZXlZmC8xCAQBAQIDBMIDVkxCZDQF/pUHwgcTCyAUQEdPU8etCAgFCQZHTFxbwLoHEwsgFEBHT1PHrQgIBQkGR0xcW8C6BxMLIBRAR09Tx60ICAUJBkdMXFvAwGQBZQMMFf6D/oYB/fkBAAABAAAAAAYhBrYALAAAASIHDgEHBhURFB4BOwERITU0Nz4BNzYyFx4BFxYdASERMzI+ATURNCcuAScmA4CJfXi6MzU8Zz3g/tUpKJFeYdRhXpEoKf7V4D1nPDUzunh9BrU0M7t4fYn99j1nPAJVlWthXpAoKSkokF5ha5X9qzxnPQIKiX14uzM0AAAAAAIAAAAABUAFQAACAAYAAAkCIREzEQHAAnv9hQLrlQHAAcABwPyAA4AAAAAAAgAAAAAFQAVAAAMABgAAATMRIwkBEQHAlZUBBQJ7BUD8gAHA/kADgAAAAAAAABAAxgABAAAAAAABAAcAAAABAAAAAAACAAcABwABAAAAAAADAAcADgABAAAAAAAEAAcAFQABAAAAAAAFAAsAHAABAAAAAAAGAAcAJwABAAAAAAAKACsALgABAAAAAAALABMAWQADAAEECQABAA4AbAADAAEECQACAA4AegADAAEECQADAA4AiAADAAEECQAEAA4AlgADAAEECQAFABYApAADAAEECQAGAA4AugADAAEECQAKAFYAyAADAAEECQALACYBHlZpZGVvSlNSZWd1bGFyVmlkZW9KU1ZpZGVvSlNWZXJzaW9uIDEuMFZpZGVvSlNHZW5lcmF0ZWQgYnkgc3ZnMnR0ZiBmcm9tIEZvbnRlbGxvIHByb2plY3QuaHR0cDovL2ZvbnRlbGxvLmNvbQBWAGkAZABlAG8ASgBTAFIAZQBnAHUAbABhAHIAVgBpAGQAZQBvAEoAUwBWAGkAZABlAG8ASgBTAFYAZQByAHMAaQBvAG4AIAAxAC4AMABWAGkAZABlAG8ASgBTAEcAZQBuAGUAcgBhAHQAZQBkACAAYgB5ACAAcwB2AGcAMgB0AHQAZgAgAGYAcgBvAG0AIABGAG8AbgB0AGUAbABsAG8AIABwAHIAbwBqAGUAYwB0AC4AaAB0AHQAcAA6AC8ALwBmAG8AbgB0AGUAbABsAG8ALgBjAG8AbQAAAAIAAAAAAAAAEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQECAQMBBAEFAQYBBwEIAQkBCgELAQwBDQEOAQ8BEAERARIBEwEUARUBFgEXARgBGQEaARsBHAEdAR4BHwEgASEBIgAEcGxheQtwbGF5LWNpcmNsZQVwYXVzZQt2b2x1bWUtbXV0ZQp2b2x1bWUtbG93CnZvbHVtZS1taWQLdm9sdW1lLWhpZ2gQZnVsbHNjcmVlbi1lbnRlcg9mdWxsc2NyZWVuLWV4aXQGc3F1YXJlB3NwaW5uZXIJc3VidGl0bGVzCGNhcHRpb25zCGNoYXB0ZXJzBXNoYXJlA2NvZwZjaXJjbGUOY2lyY2xlLW91dGxpbmUTY2lyY2xlLWlubmVyLWNpcmNsZQJoZAZjYW5jZWwGcmVwbGF5CGZhY2Vib29rBWdwbHVzCGxpbmtlZGluB3R3aXR0ZXIGdHVtYmxyCXBpbnRlcmVzdBFhdWRpby1kZXNjcmlwdGlvbgVhdWRpbwluZXh0LWl0ZW0NcHJldmlvdXMtaXRlbQAAAAA=) format("truetype"); font-weight: normal; font-style: normal; } @@ -238,6 +238,20 @@ .vjs-icon-audio:before, .video-js .vjs-audio-button .vjs-icon-placeholder:before { content: "\f11e"; } +.vjs-icon-next-item { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-next-item:before { + content: "\f11f"; } + +.vjs-icon-previous-item { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-previous-item:before { + content: "\f120"; } + .video-js { display: block; vertical-align: top; @@ -250,7 +264,8 @@ line-height: 1; font-weight: normal; font-style: normal; - font-family: Arial, Helvetica, sans-serif; } + font-family: Arial, Helvetica, sans-serif; + word-break: initial; } .video-js:-moz-full-screen { position: absolute; } .video-js:-webkit-full-screen { @@ -421,6 +436,10 @@ body.vjs-full-window { -moz-appearance: none; appearance: none; } +.vjs-control .vjs-button { + width: 100%; + height: 100%; } + .video-js .vjs-control.vjs-close-button { cursor: pointer; height: 3em; @@ -675,6 +694,9 @@ body.vjs-full-window { align-items: center; min-width: 4em; } +.video-js .vjs-progress-control.disabled { + cursor: default; } + .vjs-live .vjs-progress-control { display: none; } @@ -700,6 +722,9 @@ body.vjs-full-window { .video-js .vjs-progress-control:hover .vjs-progress-holder { font-size: 1.666666666666666666em; } +.video-js .vjs-progress-control:hover .vjs-progress-holder.disabled { + font-size: 1em; } + .video-js .vjs-progress-holder .vjs-play-progress, .video-js .vjs-progress-holder .vjs-load-progress, .video-js .vjs-progress-holder .vjs-load-progress div { @@ -755,6 +780,9 @@ body.vjs-full-window { font-size: 0.6em; visibility: visible; } +.video-js .vjs-progress-control.disabled:hover .vjs-time-tooltip { + font-size: 1em; } + .video-js .vjs-progress-control .vjs-mouse-display { display: none; position: absolute; @@ -791,13 +819,24 @@ body.vjs-full-window { cursor: pointer; padding: 0; margin: 0 0.45em 0 0.45em; + /* iOS Safari */ + -webkit-touch-callout: none; + /* Safari */ -webkit-user-select: none; + /* Konqueror HTML */ + -khtml-user-select: none; + /* Firefox */ -moz-user-select: none; + /* Internet Explorer/Edge */ -ms-user-select: none; + /* Non-prefixed version, currently supported by Chrome and Opera */ user-select: none; background-color: #73859f; background-color: rgba(115, 133, 159, 0.5); } +.video-js .vjs-slider.disabled { + cursor: default; } + .video-js .vjs-slider:focus { text-shadow: 0em 0em 1em white; -webkit-box-shadow: 0 0 1em #fff; @@ -850,10 +889,7 @@ body.vjs-full-window { .video-js .vjs-volume-panel:focus .vjs-volume-control, .video-js .vjs-volume-panel .vjs-volume-control:hover, .video-js .vjs-volume-panel .vjs-volume-control:active, - .video-js .vjs-volume-panel .vjs-volume-control:focus, .video-js .vjs-volume-panel .vjs-mute-control:hover ~ .vjs-volume-control, - .video-js .vjs-volume-panel .vjs-mute-control:active ~ .vjs-volume-control, - .video-js .vjs-volume-panel .vjs-mute-control:focus ~ .vjs-volume-control, .video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active { visibility: visible; opacity: 1; @@ -868,10 +904,7 @@ body.vjs-full-window { .video-js .vjs-volume-panel:focus .vjs-volume-control.vjs-volume-horizontal, .video-js .vjs-volume-panel .vjs-volume-control:hover.vjs-volume-horizontal, .video-js .vjs-volume-panel .vjs-volume-control:active.vjs-volume-horizontal, - .video-js .vjs-volume-panel .vjs-volume-control:focus.vjs-volume-horizontal, .video-js .vjs-volume-panel .vjs-mute-control:hover ~ .vjs-volume-control.vjs-volume-horizontal, - .video-js .vjs-volume-panel .vjs-mute-control:active ~ .vjs-volume-control.vjs-volume-horizontal, - .video-js .vjs-volume-panel .vjs-mute-control:focus ~ .vjs-volume-control.vjs-volume-horizontal, .video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active.vjs-volume-horizontal { width: 5em; height: 3em; } @@ -880,10 +913,7 @@ body.vjs-full-window { .video-js .vjs-volume-panel:focus .vjs-volume-control.vjs-volume-vertical, .video-js .vjs-volume-panel .vjs-volume-control:hover.vjs-volume-vertical, .video-js .vjs-volume-panel .vjs-volume-control:active.vjs-volume-vertical, - .video-js .vjs-volume-panel .vjs-volume-control:focus.vjs-volume-vertical, .video-js .vjs-volume-panel .vjs-mute-control:hover ~ .vjs-volume-control.vjs-volume-vertical, - .video-js .vjs-volume-panel .vjs-mute-control:active ~ .vjs-volume-control.vjs-volume-vertical, - .video-js .vjs-volume-panel .vjs-mute-control:focus ~ .vjs-volume-control.vjs-volume-vertical, .video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active.vjs-volume-vertical { -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; } .video-js .vjs-volume-panel:hover .vjs-volume-control.vjs-volume-vertical .vjs-volume-bar, @@ -896,18 +926,12 @@ body.vjs-full-window { .video-js .vjs-volume-panel .vjs-volume-control:hover.vjs-volume-vertical .vjs-volume-level, .video-js .vjs-volume-panel .vjs-volume-control:active.vjs-volume-vertical .vjs-volume-bar, .video-js .vjs-volume-panel .vjs-volume-control:active.vjs-volume-vertical .vjs-volume-level, - .video-js .vjs-volume-panel .vjs-volume-control:focus.vjs-volume-vertical .vjs-volume-bar, - .video-js .vjs-volume-panel .vjs-volume-control:focus.vjs-volume-vertical .vjs-volume-level, .video-js .vjs-volume-panel .vjs-mute-control:hover ~ .vjs-volume-control.vjs-volume-vertical .vjs-volume-bar, .video-js .vjs-volume-panel .vjs-mute-control:hover ~ .vjs-volume-control.vjs-volume-vertical .vjs-volume-level, - .video-js .vjs-volume-panel .vjs-mute-control:active ~ .vjs-volume-control.vjs-volume-vertical .vjs-volume-bar, - .video-js .vjs-volume-panel .vjs-mute-control:active ~ .vjs-volume-control.vjs-volume-vertical .vjs-volume-level, - .video-js .vjs-volume-panel .vjs-mute-control:focus ~ .vjs-volume-control.vjs-volume-vertical .vjs-volume-bar, - .video-js .vjs-volume-panel .vjs-mute-control:focus ~ .vjs-volume-control.vjs-volume-vertical .vjs-volume-level, .video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active.vjs-volume-vertical .vjs-volume-bar, .video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active.vjs-volume-vertical .vjs-volume-level { -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; } - .video-js .vjs-volume-panel.vjs-volume-panel-horizontal:hover, .video-js .vjs-volume-panel.vjs-volume-panel-horizontal:focus, .video-js .vjs-volume-panel.vjs-volume-panel-horizontal:active, .video-js .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active { + .video-js .vjs-volume-panel.vjs-volume-panel-horizontal:hover, .video-js .vjs-volume-panel.vjs-volume-panel-horizontal:active, .video-js .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active { width: 9em; -webkit-transition: width 0.1s; -moz-transition: width 0.1s; @@ -1156,14 +1180,18 @@ video::-webkit-media-text-track-display { -ms-flex: none; flex: none; } +.vjs-playback-rate > .vjs-menu-button, .vjs-playback-rate .vjs-playback-rate-value { - font-size: 1.5em; - line-height: 2; position: absolute; top: 0; left: 0; width: 100%; - height: 100%; + height: 100%; } + +.vjs-playback-rate .vjs-playback-rate-value { + pointer-events: none; + font-size: 1.5em; + line-height: 2; text-align: center; } .vjs-playback-rate .vjs-menu { @@ -1202,11 +1230,13 @@ video::-webkit-media-text-track-display { background-clip: padding-box; width: 50px; height: 50px; - border-radius: 25px; } + border-radius: 25px; + visibility: hidden; } .vjs-seeking .vjs-loading-spinner, .vjs-waiting .vjs-loading-spinner { - display: block; } + display: block; + animation: 0s linear 0.3s forwards vjs-spinner-show; } .vjs-loading-spinner:before, .vjs-loading-spinner:after { @@ -1239,6 +1269,14 @@ video::-webkit-media-text-track-display { -webkit-animation-delay: 0.44s; animation-delay: 0.44s; } +@keyframes vjs-spinner-show { + to { + visibility: visible; } } + +@-webkit-keyframes vjs-spinner-show { + to { + visibility: visible; } } + @keyframes vjs-spinner-spin { 100% { transform: rotate(360deg); } } @@ -1380,6 +1418,15 @@ video::-webkit-media-text-track-display { .video-js > *:not(.vjs-tech):not(.vjs-poster) { visibility: hidden; } } +.vjs-resize-manager { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border: none; + visibility: hidden; } + @media \0screen { .vjs-user-inactive.vjs-playing .vjs-control-bar :before { content: ""; diff --git a/server/user.jobengine.osgi.server/js/6.1.0-video.js b/server/user.jobengine.osgi.server/js/6.1.0-video.js new file mode 100644 index 00000000..101006a4 --- /dev/null +++ b/server/user.jobengine.osgi.server/js/6.1.0-video.js @@ -0,0 +1,27410 @@ +/** + * @license + * Video.js 6.1.0 + * Copyright Brightcove, Inc. + * Available under Apache License Version 2.0 + * + * + * Includes vtt.js + * Available under Apache License Version 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.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 1 && arguments[1] !== undefined ? arguments[1] : {}; + var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + + tag = 'button'; + + props = (0, _obj.assign)({ + innerHTML: '', + className: this.buildCSSClass() + }, props); + + // Add attributes for button element + attributes = (0, _obj.assign)({ + + // Necessary since the default button type is "submit" + 'type': 'button', + + // let the screen reader user know that the text of the button may change + 'aria-live': 'polite' + }, attributes); + + var el = _component2['default'].prototype.createEl.call(this, tag, props, attributes); + + this.createControlTextEl(el); + + return el; + }; + + /** + * Add a child `Component` inside of this `Button`. + * + * @param {string|Component} child + * The name or instance of a child to add. + * + * @param {Object} [options={}] + * The key/value store of options that will get passed to children of + * the child. + * + * @return {Component} + * The `Component` that gets added as a child. When using a string the + * `Component` will get created by this process. + * + * @deprecated since version 5 + */ + + + Button.prototype.addChild = function addChild(child) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + var className = this.constructor.name; + + _log2['default'].warn('Adding an actionable (user controllable) child to a Button (' + className + ') is not supported; use a ClickableComponent instead.'); + + // Avoid the error message generated by ClickableComponent's addChild method + return _component2['default'].prototype.addChild.call(this, child, options); + }; + + /** + * Enable the `Button` element so that it can be activated or clicked. Use this with + * {@link Button#disable}. + */ + + + Button.prototype.enable = function enable() { + _ClickableComponent.prototype.enable.call(this); + this.el_.removeAttribute('disabled'); + }; + + /** + * Enable the `Button` element so that it cannot be activated or clicked. Use this with + * {@link Button#enable}. + */ + + + Button.prototype.disable = function disable() { + _ClickableComponent.prototype.disable.call(this); + this.el_.setAttribute('disabled', 'disabled'); + }; + + /** + * This gets called when a `Button` has focus and `keydown` is triggered via a key + * press. + * + * @param {EventTarget~Event} event + * The event that caused this function to get called. + * + * @listens keydown + */ + + + 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) { + return; + } + + // Pass keypress handling up for unsupported keys + _ClickableComponent.prototype.handleKeyPress.call(this, event); + }; + + return Button; +}(_clickableComponent2['default']); + +_component2['default'].registerComponent('Button', Button); +exports['default'] = Button; + +},{"3":3,"5":5,"91":91,"93":93}],3:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _dom = _dereq_(85); + +var Dom = _interopRequireWildcard(_dom); + +var _events = _dereq_(86); + +var Events = _interopRequireWildcard(_events); + +var _fn = _dereq_(88); + +var Fn = _interopRequireWildcard(_fn); + +var _log = _dereq_(91); + +var _log2 = _interopRequireDefault(_log); + +var _document = _dereq_(99); + +var _document2 = _interopRequireDefault(_document); + +var _obj = _dereq_(93); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file button.js + */ + + +/** + * Clickable Component which is clickable or keyboard actionable, + * but is not a native HTML button. + * + * @extends Component + */ +var ClickableComponent = function (_Component) { + _inherits(ClickableComponent, _Component); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function ClickableComponent(player, options) { + _classCallCheck(this, ClickableComponent); + + var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); + + _this.emitTapEvents(); + + _this.enable(); + return _this; + } + + /** + * Create the `Component`s DOM element. + * + * @param {string} [tag=div] + * The element's node type. + * + * @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} + * The element that gets created. + */ + + + ClickableComponent.prototype.createEl = function createEl() { + var tag = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'div'; + var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + + props = (0, _obj.assign)({ + innerHTML: '', + className: this.buildCSSClass(), + tabIndex: 0 + }, props); + + if (tag === 'button') { + _log2['default'].error('Creating a ClickableComponent with an HTML element of ' + tag + ' is not supported; use a Button instead.'); + } + + // Add ARIA attributes for clickable element which is not a native HTML button + attributes = (0, _obj.assign)({ + 'role': 'button', + + // let the screen reader user know that the text of the element may change + 'aria-live': 'polite' + }, attributes); + + this.tabIndex_ = props.tabIndex; + + var el = _Component.prototype.createEl.call(this, tag, props, attributes); + + this.createControlTextEl(el); + + return el; + }; + + /** + * Create a control text element on this `Component` + * + * @param {Element} [el] + * Parent element for the control text. + * + * @return {Element} + * The control text element that gets created. + */ + + + ClickableComponent.prototype.createControlTextEl = function createControlTextEl(el) { + this.controlTextEl_ = Dom.createEl('span', { + className: 'vjs-control-text' + }); + + if (el) { + el.appendChild(this.controlTextEl_); + } + + this.controlText(this.controlText_, el); + + return this.controlTextEl_; + }; + + /** + * Get or set the localize text to use for the controls on the `Component`. + * + * @param {string} [text] + * Control text for element. + * + * @param {Element} [el=this.el()] + * Element to set the title on. + * + * @return {string} + * - The control text when getting + */ + + + ClickableComponent.prototype.controlText = function controlText(text) { + var el = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.el(); + + if (!text) { + return this.controlText_ || 'Need Text'; + } + + var localizedText = this.localize(text); + + this.controlText_ = text; + this.controlTextEl_.innerHTML = localizedText; + if (!this.nonIconControl) { + // Set title attribute if only an icon is shown + el.setAttribute('title', localizedText); + } + }; + + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ + + + ClickableComponent.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-control vjs-button ' + _Component.prototype.buildCSSClass.call(this); + }; + + /** + * Enable this `Component`s element. + */ + + + ClickableComponent.prototype.enable = function enable() { + if (!this.enabled_) { + this.enabled_ = true; + this.removeClass('vjs-disabled'); + this.el_.setAttribute('aria-disabled', 'false'); + if (typeof this.tabIndex_ !== 'undefined') { + this.el_.setAttribute('tabIndex', this.tabIndex_); + } + this.on(['tap', 'click'], this.handleClick); + this.on('focus', this.handleFocus); + this.on('blur', this.handleBlur); + } + }; + + /** + * Disable this `Component`s element. + */ + + + ClickableComponent.prototype.disable = function disable() { + this.enabled_ = false; + this.addClass('vjs-disabled'); + this.el_.setAttribute('aria-disabled', 'true'); + if (typeof this.tabIndex_ !== 'undefined') { + this.el_.removeAttribute('tabIndex'); + } + this.off(['tap', 'click'], this.handleClick); + this.off('focus', this.handleFocus); + this.off('blur', this.handleBlur); + }; + + /** + * This gets called when a `ClickableComponent` gets: + * - Clicked (via the `click` event, listening starts in the constructor) + * - Tapped (via the `tap` event, listening starts in the constructor) + * - The following things happen in order: + * 1. {@link ClickableComponent#handleFocus} is called via a `focus` event on the + * `ClickableComponent`. + * 2. {@link ClickableComponent#handleFocus} adds a listener for `keydown` on using + * {@link ClickableComponent#handleKeyPress}. + * 3. `ClickableComponent` has not had a `blur` event (`blur` means that focus was lost). The user presses + * the space or enter key. + * 4. {@link ClickableComponent#handleKeyPress} calls this function with the `keydown` + * event as a parameter. + * + * @param {EventTarget~Event} event + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click + * @abstract + */ + + + ClickableComponent.prototype.handleClick = function handleClick(event) {}; + + /** + * This gets called when a `ClickableComponent` gains focus via a `focus` event. + * Turns on listening for `keydown` events. When they happen it + * calls `this.handleKeyPress`. + * + * @param {EventTarget~Event} event + * The `focus` event that caused this function to be called. + * + * @listens focus + */ + + + ClickableComponent.prototype.handleFocus = function handleFocus(event) { + Events.on(_document2['default'], 'keydown', Fn.bind(this, this.handleKeyPress)); + }; + + /** + * Called when this ClickableComponent has focus and a key gets pressed down. By + * default it will call `this.handleClick` when the key is space or enter. + * + * @param {EventTarget~Event} event + * The `keydown` event that caused this function to be called. + * + * @listens keydown + */ + + + 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.trigger('click'); + } else if (_Component.prototype.handleKeyPress) { + + // Pass keypress handling up for unsupported keys + _Component.prototype.handleKeyPress.call(this, event); + } + }; + + /** + * Called when a `ClickableComponent` loses focus. Turns off the listener for + * `keydown` events. Which Stops `this.handleKeyPress` from getting called. + * + * @param {EventTarget~Event} event + * The `blur` event that caused this function to be called. + * + * @listens blur + */ + + + ClickableComponent.prototype.handleBlur = function handleBlur(event) { + Events.off(_document2['default'], 'keydown', Fn.bind(this, this.handleKeyPress)); + }; + + return ClickableComponent; +}(_component2['default']); + +_component2['default'].registerComponent('ClickableComponent', ClickableComponent); +exports['default'] = ClickableComponent; + +},{"5":5,"85":85,"86":86,"88":88,"91":91,"93":93,"99":99}],4:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _button = _dereq_(2); + +var _button2 = _interopRequireDefault(_button); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file close-button.js + */ + + +/** + * The `CloseButton` is a `{@link Button}` that fires a `close` event when + * it gets clicked. + * + * @extends Button + */ +var CloseButton = function (_Button) { + _inherits(CloseButton, _Button); + + /** + * Creates an instance of the this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function CloseButton(player, options) { + _classCallCheck(this, CloseButton); + + var _this = _possibleConstructorReturn(this, _Button.call(this, player, options)); + + _this.controlText(options && options.controlText || _this.localize('Close')); + return _this; + } + + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ + + + CloseButton.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-close-button ' + _Button.prototype.buildCSSClass.call(this); + }; + + /** + * This gets called when a `CloseButton` gets clicked. See + * {@link ClickableComponent#handleClick} for more information on when this will be + * triggered + * + * @param {EventTarget~Event} event + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click + * @fires CloseButton#close + */ + + + CloseButton.prototype.handleClick = function handleClick(event) { + + /** + * Triggered when the a `CloseButton` is clicked. + * + * @event CloseButton#close + * @type {EventTarget~Event} + * + * @property {boolean} [bubbles=false] + * set to false so that the close event does not + * bubble up to parents if there is no listener + */ + this.trigger({ type: 'close', bubbles: false }); + }; + + return CloseButton; +}(_button2['default']); + +_component2['default'].registerComponent('CloseButton', CloseButton); +exports['default'] = CloseButton; + +},{"2":2,"5":5}],5:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _window = _dereq_(100); + +var _window2 = _interopRequireDefault(_window); + +var _evented = _dereq_(53); + +var _evented2 = _interopRequireDefault(_evented); + +var _stateful = _dereq_(54); + +var _stateful2 = _interopRequireDefault(_stateful); + +var _dom = _dereq_(85); + +var Dom = _interopRequireWildcard(_dom); + +var _domData = _dereq_(84); + +var DomData = _interopRequireWildcard(_domData); + +var _fn = _dereq_(88); + +var Fn = _interopRequireWildcard(_fn); + +var _guid = _dereq_(90); + +var Guid = _interopRequireWildcard(_guid); + +var _log = _dereq_(91); + +var _log2 = _interopRequireDefault(_log); + +var _toTitleCase = _dereq_(96); + +var _toTitleCase2 = _interopRequireDefault(_toTitleCase); + +var _mergeOptions = _dereq_(92); + +var _mergeOptions2 = _interopRequireDefault(_mergeOptions); + +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"); } } /** + * Player Component - Base class for all UI objects + * + * @file component.js + */ + + +/** + * Base class for all UI Components. + * Components are UI objects which represent both a javascript object and an element + * in the DOM. They can be children of other components, and can have + * children themselves. + * + * Components can also use methods from {@link EventTarget} + */ +var Component = function () { + + /** + * A callback that is called when a component is ready. Does not have any + * paramters and any callback value will be ignored. + * + * @callback Component~ReadyCallback + * @this Component + */ + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + * + * @param {Object[]} [options.children] + * An array of children objects to intialize this component with. Children objects have + * a name property that will be used if more than one component of the same type needs to be + * added. + * + * @param {Component~ReadyCallback} [ready] + * Function that gets called when the `Component` is ready. + */ + function Component(player, options, ready) { + _classCallCheck(this, Component); + + // 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; + } + + // Make a copy of prototype.options_ to protect against overriding defaults + this.options_ = (0, _mergeOptions2['default'])({}, this.options_); + + // Updated options with supplied options + options = this.options_ = (0, _mergeOptions2['default'])(this.options_, options); + + // Get ID from options or options element if one is supplied + this.id_ = options.id || options.el && options.el.id; + + // 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'; + + this.id_ = id + '_component_' + Guid.newGUID(); + } + + this.name_ = options.name || null; + + // 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(); + } + + // Make this an evented object and use `el_`, if available, as its event bus + (0, _evented2['default'])(this, { eventBusKey: this.el_ ? 'el_' : null }); + (0, _stateful2['default'])(this, this.constructor.defaultState); + + this.children_ = []; + this.childIndex_ = {}; + this.childNameIndex_ = {}; + + // Add any child components in options + if (options.initChildren !== false) { + this.initChildren(); + } + + 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 + + if (options.reportTouchActivity !== false) { + this.enableTouchActivity(); + } + } + + /** + * Dispose of the `Component` and all child components. + * + * @fires Component#dispose + */ + + + Component.prototype.dispose = function dispose() { + + /** + * Triggered when a `Component` is disposed. + * + * @event Component#dispose + * @type {EventTarget~Event} + * + * @property {boolean} [bubbles=false] + * set to false so that the close event does not + * bubble up + */ + this.trigger({ type: 'dispose', bubbles: false }); + + // 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(); + } + } + } + + // Delete child references + this.children_ = null; + this.childIndex_ = null; + this.childNameIndex_ = null; + + if (this.el_) { + // Remove element from DOM + if (this.el_.parentNode) { + this.el_.parentNode.removeChild(this.el_); + } + + DomData.removeData(this.el_); + this.el_ = null; + } + }; + + /** + * Return the {@link Player} that the `Component` has attached to. + * + * @return {Player} + * The player that this `Component` has attached to. + */ + + + Component.prototype.player = function player() { + return this.player_; + }; + + /** + * Deep merge of options objects with new options. + * > Note: When both `obj` and `options` contain properties whose values are objects. + * The two properties get merged using {@link module:mergeOptions} + * + * @param {Object} obj + * The object that contains new options. + * + * @return {Object} + * A new object of `this.options_` and `obj` merged together. + * + * @deprecated since version 5 + */ + + + Component.prototype.options = function options(obj) { + _log2['default'].warn('this.options() has been deprecated and will be moved to the constructor in 6.0'); + + if (!obj) { + return this.options_; + } + + this.options_ = (0, _mergeOptions2['default'])(this.options_, obj); + return this.options_; + }; + + /** + * Get the `Component`s DOM element + * + * @return {Element} + * The DOM element for this `Component`. + */ + + + Component.prototype.el = function el() { + return this.el_; + }; + + /** + * Create the `Component`s DOM element. + * + * @param {string} [tagName] + * Element's DOM 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} + * The element that gets created. + */ + + + Component.prototype.createEl = function createEl(tagName, properties, attributes) { + return Dom.createEl(tagName, properties, attributes); + }; + + /** + * Localize a string given the string in english. + * + * If tokens are provided, it'll try and run a simple token replacement on the provided string. + * The tokens it loooks for look like `{1}` with the index being 1-indexed into the tokens array. + * + * If a `defaultValue` is provided, it'll use that over `string`, + * if a value isn't found in provided language files. + * This is useful if you want to have a descriptive key for token replacement + * but have a succinct localized string and not require `en.json` to be included. + * + * Currently, it is used for the progress bar timing. + * ```js + * { + * "progress bar timing: currentTime={1} duration={2}": "{1} of {2}" + * } + * ``` + * It is then used like so: + * ```js + * this.localize('progress bar timing: currentTime={1} duration{2}', + * [this.player_.currentTime(), this.player_.duration()], + * '{1} of {2}'); + * ``` + * + * Which outputs something like: `01:23 of 24:56`. + * + * + * @param {string} string + * The string to localize and the key to lookup in the language files. + * @param {string[]} [tokens] + * If the current item has token replacements, provide the tokens here. + * @param {string} [defaultValue] + * Defaults to `string`. Can be a default value to use for token replacement + * if the lookup key is needed to be separate. + * + * @return {string} + * The localized string or if no localization exists the english string. + */ + + + Component.prototype.localize = function localize(string, tokens) { + var defaultValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : string; + + var code = this.player_.language && this.player_.language(); + var languages = this.player_.languages && this.player_.languages(); + var language = languages && languages[code]; + var primaryCode = code && code.split('-')[0]; + var primaryLang = languages && languages[primaryCode]; + + var localizedString = defaultValue; + + if (language && language[string]) { + localizedString = language[string]; + } else if (primaryLang && primaryLang[string]) { + localizedString = primaryLang[string]; + } + + if (tokens) { + localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) { + var value = tokens[index - 1]; + var ret = value; + + if (typeof value === 'undefined') { + ret = match; + } + + return ret; + }); + } + + return localizedString; + }; + + /** + * Return the `Component`s DOM element. This is where children get inserted. + * This will usually be the the same as the element returned in {@link Component#el}. + * + * @return {Element} + * The content element for this `Component`. + */ + + + Component.prototype.contentEl = function contentEl() { + return this.contentEl_ || this.el_; + }; + + /** + * Get this `Component`s ID + * + * @return {string} + * The id of this `Component` + */ + + + Component.prototype.id = function id() { + return this.id_; + }; + + /** + * Get the `Component`s name. The name gets used to reference the `Component` + * and is set during registration. + * + * @return {string} + * The name of this `Component`. + */ + + + Component.prototype.name = function name() { + return this.name_; + }; + + /** + * Get an array of all child components + * + * @return {Array} + * The children + */ + + + Component.prototype.children = function children() { + return this.children_; + }; + + /** + * Returns the child `Component` with the given `id`. + * + * @param {string} id + * The id of the child `Component` to get. + * + * @return {Component|undefined} + * The child `Component` with the given `id` or undefined. + */ + + + Component.prototype.getChildById = function getChildById(id) { + return this.childIndex_[id]; + }; + + /** + * Returns the child `Component` with the given `name`. + * + * @param {string} name + * The name of the child `Component` to get. + * + * @return {Component|undefined} + * The child `Component` with the given `name` or undefined. + */ + + + Component.prototype.getChild = function getChild(name) { + if (!name) { + return; + } + + name = (0, _toTitleCase2['default'])(name); + + return this.childNameIndex_[name]; + }; + + /** + * Add a child `Component` inside the current `Component`. + * + * + * @param {string|Component} child + * The name or instance of a child to add. + * + * @param {Object} [options={}] + * The key/value store of options that will get passed to children of + * the child. + * + * @param {number} [index=this.children_.length] + * The index to attempt to add a child into. + * + * @return {Component} + * The `Component` that gets added as a child. When using a string the + * `Component` will get created by this process. + */ + + + Component.prototype.addChild = function addChild(child) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + var index = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.children_.length; + + var component = void 0; + var componentName = void 0; + + // If child is a string, create component with options + if (typeof child === 'string') { + componentName = (0, _toTitleCase2['default'])(child); + + var componentClassName = options.componentClass || componentName; + + // Set name through options + options.name = componentName; + + // Create a new object & element for this controls set + // If there's no .player_, this is a player + var ComponentClass = Component.getComponent(componentClassName); + + if (!ComponentClass) { + throw new Error('Component ' + componentClassName + ' does not exist'); + } + + // 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; + } + + component = new ComponentClass(this.player_ || this, options); + + // child is a component instance + } else { + component = child; + } + + this.children_.splice(index, 0, component); + + if (typeof component.id === 'function') { + this.childIndex_[component.id()] = 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 && (0, _toTitleCase2['default'])(component.name()); + + if (componentName) { + this.childNameIndex_[componentName] = component; + } + + // 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); + } + + // Return so it can stored on parent object if desired. + return component; + }; + + /** + * Remove a child `Component` from this `Component`s list of children. Also removes + * the child `Component`s element from this `Component`s element. + * + * @param {Component} component + * The child `Component` to remove. + */ + + + Component.prototype.removeChild = function removeChild(component) { + if (typeof component === 'string') { + component = this.getChild(component); + } + + if (!component || !this.children_) { + return; + } + + 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; + } + } + + if (!childFound) { + return; + } + + this.childIndex_[component.id()] = null; + this.childNameIndex_[component.name()] = null; + + var compEl = component.el(); + + if (compEl && compEl.parentNode === this.contentEl()) { + this.contentEl().removeChild(component.el()); + } + }; + + /** + * Add and initialize default child `Component`s based upon options. + */ + + + Component.prototype.initChildren = function initChildren() { + var _this = this; + + var children = this.options_.children; + + if (children) { + // `this` is `parent` + var parentOptions = this.options_; + + var handleAdd = function handleAdd(child) { + var name = child.name; + var opts = child.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]; + } + + // Allow for disabling default components + // e.g. options['children']['posterImage'] = false + if (opts === false) { + return; + } + + // Allow options to be passed as a simple boolean if no configuration + // is necessary. + if (opts === true) { + opts = {}; + } + + // We also want to pass the original player options + // to each component as well so they don't need to + // reach back into the player for options later. + opts.playerOptions = _this.options_.playerOptions; + + // 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 + var newChild = _this.addChild(name, opts); + + if (newChild) { + _this[name] = newChild; + } + }; + + // Allow for an array of children details to passed in the options + var workingChildren = void 0; + var Tech = Component.getComponent('Tech'); + + if (Array.isArray(children)) { + workingChildren = children; + } else { + workingChildren = Object.keys(children); + } + + workingChildren + // children that are in this.options_ but also in workingChildren would + // give us extra children we do not want. So, we want to filter them out. + .concat(Object.keys(this.options_).filter(function (child) { + return !workingChildren.some(function (wchild) { + if (typeof wchild === 'string') { + return child === wchild; + } + return child === wchild.name; + }); + })).map(function (child) { + var name = void 0; + var opts = void 0; + + if (typeof child === 'string') { + name = child; + opts = children[name] || _this.options_[name] || {}; + } else { + name = child.name; + opts = child; + } + + return { name: name, opts: opts }; + }).filter(function (child) { + // we have to make sure that child.name isn't in the techOrder since + // techs are registerd as Components but can't aren't compatible + // See https://github.com/videojs/video.js/issues/2772 + var c = Component.getComponent(child.opts.componentClass || (0, _toTitleCase2['default'])(child.name)); + + return c && !Tech.isTech(c); + }).forEach(handleAdd); + } + }; + + /** + * Builds the default DOM class name. Should be overriden by sub-components. + * + * @return {string} + * The DOM class name for this object. + * + * @abstract + */ + + + Component.prototype.buildCSSClass = function buildCSSClass() { + // Child classes can include a function that does: + // return 'CLASS NAME' + this._super(); + return ''; + }; + + /** + * 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. + * + * @return {Component} + * Returns itself; method can be chained. + */ + + + Component.prototype.ready = function ready(fn) { + var sync = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + + 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.readyQueue_ = this.readyQueue_ || []; + this.readyQueue_.push(fn); + } + } + }; + + /** + * Trigger all the ready listeners for this `Component`. + * + * @fires Component#ready + */ + + + Component.prototype.triggerReady = function triggerReady() { + this.isReady_ = true; + + // Ensure ready is triggerd asynchronously + this.setTimeout(function () { + var readyQueue = this.readyQueue_; + + // Reset Ready Queue + this.readyQueue_ = []; + + if (readyQueue && readyQueue.length > 0) { + readyQueue.forEach(function (fn) { + fn.call(this); + }, this); + } + + // Allow for using event listeners also + /** + * Triggered when a `Component` is ready. + * + * @event Component#ready + * @type {EventTarget~Event} + */ + this.trigger('ready'); + }, 1); + }; + + /** + * Find a single DOM element matching a `selector`. This can be within the `Component`s + * `contentEl()` or another custom context. + * + * @param {string} selector + * A valid CSS selector, which will be passed to `querySelector`. + * + * @param {Element|string} [context=this.contentEl()] + * A DOM element within which to query. Can also be a selector string in + * which case the first matching element will get used as context. If + * missing `this.contentEl()` gets used. If `this.contentEl()` returns + * nothing it falls back to `document`. + * + * @return {Element|null} + * the dom element that was found, or null + * + * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) + */ + + + Component.prototype.$ = function $(selector, context) { + return Dom.$(selector, context || this.contentEl()); + }; + + /** + * Finds all DOM element matching a `selector`. This can be within the `Component`s + * `contentEl()` or another custom context. + * + * @param {string} selector + * A valid CSS selector, which will be passed to `querySelectorAll`. + * + * @param {Element|string} [context=this.contentEl()] + * A DOM element within which to query. Can also be a selector string in + * which case the first matching element will get used as context. If + * missing `this.contentEl()` gets used. If `this.contentEl()` returns + * nothing it falls back to `document`. + * + * @return {NodeList} + * a list of dom elements that were found + * + * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) + */ + + + Component.prototype.$$ = function $$(selector, context) { + return Dom.$$(selector, context || this.contentEl()); + }; + + /** + * Check if a component's element has a CSS class name. + * + * @param {string} classToCheck + * CSS class name to check. + * + * @return {boolean} + * - True if the `Component` has the class. + * - False if the `Component` does not have the class` + */ + + + Component.prototype.hasClass = function hasClass(classToCheck) { + return Dom.hasClass(this.el_, classToCheck); + }; + + /** + * Add a CSS class name to the `Component`s element. + * + * @param {string} classToAdd + * CSS class name to add + */ + + + Component.prototype.addClass = function addClass(classToAdd) { + Dom.addClass(this.el_, classToAdd); + }; + + /** + * Remove a CSS class name from the `Component`s element. + * + * @param {string} classToRemove + * CSS class name to remove + */ + + + Component.prototype.removeClass = function removeClass(classToRemove) { + Dom.removeClass(this.el_, classToRemove); + }; + + /** + * Add or remove a CSS class name from the component's element. + * - `classToToggle` gets added when {@link Component#hasClass} would return false. + * - `classToToggle` gets removed when {@link Component#hasClass} would return true. + * + * @param {string} classToToggle + * The class to add or remove based on (@link Component#hasClass} + * + * @param {boolean|Dom~predicate} [predicate] + * An {@link Dom~predicate} function or a boolean + */ + + + Component.prototype.toggleClass = function toggleClass(classToToggle, predicate) { + Dom.toggleClass(this.el_, classToToggle, predicate); + }; + + /** + * Show the `Component`s element if it is hidden by removing the + * 'vjs-hidden' class name from it. + */ + + + Component.prototype.show = function show() { + this.removeClass('vjs-hidden'); + }; + + /** + * Hide the `Component`s element if it is currently showing by adding the + * 'vjs-hidden` class name to it. + */ + + + Component.prototype.hide = function hide() { + this.addClass('vjs-hidden'); + }; + + /** + * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing' + * class name to it. Used during fadeIn/fadeOut. + * + * @private + */ + + + Component.prototype.lockShowing = function lockShowing() { + this.addClass('vjs-lock-showing'); + }; + + /** + * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing' + * class name from it. Used during fadeIn/fadeOut. + * + * @private + */ + + + Component.prototype.unlockShowing = function unlockShowing() { + this.removeClass('vjs-lock-showing'); + }; + + /** + * Get the value of an attribute on the `Component`s element. + * + * @param {string} attribute + * Name of the attribute to get the value from. + * + * @return {string|null} + * - The value of the attribute that was asked for. + * - Can be an empty string on some browsers if the attribute does not exist + * or has no value + * - Most browsers will return null if the attibute does not exist or has + * no value. + * + * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute} + */ + + + Component.prototype.getAttribute = function getAttribute(attribute) { + return Dom.getAttribute(this.el_, attribute); + }; + + /** + * Set the value of an attribute on the `Component`'s element + * + * @param {string} attribute + * Name of the attribute to set. + * + * @param {string} value + * Value to set the attribute to. + * + * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute} + */ + + + Component.prototype.setAttribute = function setAttribute(attribute, value) { + Dom.setAttribute(this.el_, attribute, value); + }; + + /** + * Remove an attribute from the `Component`s element. + * + * @param {string} attribute + * Name of the attribute to remove. + * + * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute} + */ + + + Component.prototype.removeAttribute = function removeAttribute(attribute) { + Dom.removeAttribute(this.el_, attribute); + }; + + /** + * Get or set the width of the component based upon the CSS styles. + * See {@link Component#dimension} for more detailed information. + * + * @param {number|string} [num] + * The width that you want to set postfixed with '%', 'px' or nothing. + * + * @param {boolean} [skipListeners] + * Skip the componentresize event trigger + * + * @return {number|string} + * The width when getting, zero if there is no width. Can be a string + * postpixed with '%' or 'px'. + */ + + + Component.prototype.width = function width(num, skipListeners) { + return this.dimension('width', num, skipListeners); + }; + + /** + * Get or set the height of the component based upon the CSS styles. + * See {@link Component#dimension} for more detailed information. + * + * @param {number|string} [num] + * The height that you want to set postfixed with '%', 'px' or nothing. + * + * @param {boolean} [skipListeners] + * Skip the componentresize event trigger + * + * @return {number|string} + * The width when getting, zero if there is no width. Can be a string + * postpixed with '%' or 'px'. + */ + + + Component.prototype.height = function height(num, skipListeners) { + return this.dimension('height', num, skipListeners); + }; + + /** + * Set both the width and height of the `Component` element at the same time. + * + * @param {number|string} width + * Width to set the `Component`s element to. + * + * @param {number|string} height + * Height to set the `Component`s element to. + */ + + + Component.prototype.dimensions = function dimensions(width, height) { + // Skip componentresize listeners on width for optimization + this.width(width, true); + this.height(height); + }; + + /** + * Get or set width or height of the `Component` element. This is the shared code + * for the {@link Component#width} and {@link Component#height}. + * + * Things to know: + * - If the width or height in an number this will return the number postfixed with 'px'. + * - If the width/height is a percent this will return the percent postfixed with '%' + * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function + * defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`. + * See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/} + * for more information + * - If you want the computed style of the component, use {@link Component#currentWidth} + * and {@link {Component#currentHeight} + * + * @fires Component#componentresize + * + * @param {string} widthOrHeight + 8 'width' or 'height' + * + * @param {number|string} [num] + 8 New dimension + * + * @param {boolean} [skipListeners] + * Skip componentresize event trigger + * + * @return {number} + * The dimension when getting or 0 if unset + */ + + + 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; + } + + // 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'; + } + + // skipListeners allows us to avoid triggering the resize event when setting both width and height + if (!skipListeners) { + /** + * Triggered when a component is resized. + * + * @event Component#componentresize + * @type {EventTarget~Event} + */ + this.trigger('componentresize'); + } + + return; + } + + // Not setting a value, so getting it + // Make sure element exists + if (!this.el_) { + return 0; + } + + // 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); + } + + // 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' + (0, _toTitleCase2['default'])(widthOrHeight)], 10); + }; + + /** + * Get the width or the height of the `Component` elements computed style. Uses + * `window.getComputedStyle`. + * + * @param {string} widthOrHeight + * A string containing 'width' or 'height'. Whichever one you want to get. + * + * @return {number} + * The dimension that gets asked for or 0 if nothing was set + * for that dimension. + */ + + + Component.prototype.currentDimension = function currentDimension(widthOrHeight) { + var computedWidthOrHeight = 0; + + if (widthOrHeight !== 'width' && widthOrHeight !== 'height') { + throw new Error('currentDimension only accepts width or height value'); + } + + if (typeof _window2['default'].getComputedStyle === 'function') { + var computedStyle = _window2['default'].getComputedStyle(this.el_); + + computedWidthOrHeight = computedStyle.getPropertyValue(widthOrHeight) || computedStyle[widthOrHeight]; + } + + // remove 'px' from variable and parse as integer + computedWidthOrHeight = parseFloat(computedWidthOrHeight); + + // if the computed value is still 0, it's possible that the browser is lying + // and we want to check the offset values. + // This code also runs on IE8 and wherever getComputedStyle doesn't exist. + if (computedWidthOrHeight === 0) { + var rule = 'offset' + (0, _toTitleCase2['default'])(widthOrHeight); + + computedWidthOrHeight = this.el_[rule]; + } + + return computedWidthOrHeight; + }; + + /** + * An object that contains width and height values of the `Component`s + * computed style. Uses `window.getComputedStyle`. + * + * @typedef {Object} Component~DimensionObject + * + * @property {number} width + * The width of the `Component`s computed style. + * + * @property {number} height + * The height of the `Component`s computed style. + */ + + /** + * Get an object that contains width and height values of the `Component`s + * computed style. + * + * @return {Component~DimensionObject} + * The dimensions of the components element + */ + + + Component.prototype.currentDimensions = function currentDimensions() { + return { + width: this.currentDimension('width'), + height: this.currentDimension('height') + }; + }; + + /** + * Get the width of the `Component`s computed style. Uses `window.getComputedStyle`. + * + * @return {number} width + * The width of the `Component`s computed style. + */ + + + Component.prototype.currentWidth = function currentWidth() { + return this.currentDimension('width'); + }; + + /** + * Get the height of the `Component`s computed style. Uses `window.getComputedStyle`. + * + * @return {number} height + * The height of the `Component`s computed style. + */ + + + Component.prototype.currentHeight = function currentHeight() { + return this.currentDimension('height'); + }; + + /** + * Set the focus to this component + */ + + + Component.prototype.focus = function focus() { + this.el_.focus(); + }; + + /** + * Remove the focus from this component + */ + + + Component.prototype.blur = function blur() { + this.el_.blur(); + }; + + /** + * Emit a 'tap' events when touch event support gets detected. This gets used to + * support toggling the controls through a tap on the video. They get enabled + * because every sub-component would have extra overhead otherwise. + * + * @private + * @fires Component#tap + * @listens Component#touchstart + * @listens Component#touchmove + * @listens Component#touchleave + * @listens Component#touchcancel + * @listens Component#touchend + */ + + + 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 = void 0; + + this.on('touchstart', function (event) { + // If more than one finger, don't consider treating this as a click + if (event.touches.length === 1) { + // Copy pageX/pageY from the object + firstTouch = { + pageX: event.touches[0].pageX, + pageY: event.touches[0].pageY + }; + // Record start time so we can detect a tap vs. "touch and hold" + touchStart = new Date().getTime(); + // Reset couldBeTap tracking + couldBeTap = true; + } + }); + + 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; + } + } + }); + + var noTap = function noTap() { + 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 + 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(); + /** + * Triggered when a `Component` is tapped. + * + * @event Component#tap + * @type {EventTarget~Event} + */ + 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) + } + } + }); + }; + + /** + * This function reports user activity whenever touch events happen. This can get + * turned off by any sub-components that wants touch events to act another way. + * + * Report user touch activity when touch events occur. User activity gets used to + * determine when controls should show/hide. It is 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 as `touchstart` and `touchend` toggle player + * controls. So touch events can't help us at the player level either. + * + * User activity gets 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. + * Furthermore a `touchmove` event and anything other than a tap, should not turn + * controls back on. + * + * @listens Component#touchstart + * @listens Component#touchmove + * @listens Component#touchend + * @listens Component#touchcancel + */ + + + 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; + } + + // listener for reporting that the user is active + var report = Fn.bind(this.player(), this.player().reportUserActivity); + + var touchHolding = void 0; + + 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); + }); + + var touchEnd = function touchEnd(event) { + report(); + // stop the interval that maintains activity if the touch is holding + this.clearInterval(touchHolding); + }; + + this.on('touchmove', report); + this.on('touchend', touchEnd); + this.on('touchcancel', touchEnd); + }; + + /** + * A callback that has no parameters and is bound into `Component`s context. + * + * @callback Component~GenericCallback + * @this Component + */ + + /** + * Creates a function that runs after an `x` millisecond timeout. This function is a + * wrapper around `window.setTimeout`. There are a few reasons to use this one + * instead though: + * 1. It gets cleared via {@link Component#clearTimeout} when + * {@link Component#dispose} gets called. + * 2. The function callback will gets turned into a {@link Component~GenericCallback} + * + * > Note: You can use `window.clearTimeout` on the id returned by this function. This + * will cause its dispose listener not to get cleaned up! Please use + * {@link Component#clearTimeout} or {@link Component#dispose}. + * + * @param {Component~GenericCallback} fn + * The function that will be run after `timeout`. + * + * @param {number} timeout + * Timeout in milliseconds to delay before executing the specified function. + * + * @return {number} + * Returns a timeout ID that gets used to identify the timeout. It can also + * get used in {@link Component#clearTimeout} to clear the timeout that + * was set. + * + * @listens Component#dispose + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout} + */ + + + Component.prototype.setTimeout = function setTimeout(fn, timeout) { + fn = Fn.bind(this, fn); + + var timeoutId = _window2['default'].setTimeout(fn, timeout); + var disposeFn = function disposeFn() { + this.clearTimeout(timeoutId); + }; + + disposeFn.guid = 'vjs-timeout-' + timeoutId; + + this.on('dispose', disposeFn); + + return timeoutId; + }; + + /** + * Clears a timeout that gets created via `window.setTimeout` or + * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout} + * use this function instead of `window.clearTimout`. If you don't your dispose + * listener will not get cleaned up until {@link Component#dispose}! + * + * @param {number} timeoutId + * The id of the timeout to clear. The return value of + * {@link Component#setTimeout} or `window.setTimeout`. + * + * @return {number} + * Returns the timeout id that was cleared. + * + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout} + */ + + + Component.prototype.clearTimeout = function clearTimeout(timeoutId) { + _window2['default'].clearTimeout(timeoutId); + + var disposeFn = function disposeFn() {}; + + disposeFn.guid = 'vjs-timeout-' + timeoutId; + + this.off('dispose', disposeFn); + + return timeoutId; + }; + + /** + * Creates a function that gets run every `x` milliseconds. This function is a wrapper + * around `window.setInterval`. There are a few reasons to use this one instead though. + * 1. It gets cleared via {@link Component#clearInterval} when + * {@link Component#dispose} gets called. + * 2. The function callback will be a {@link Component~GenericCallback} + * + * @param {Component~GenericCallback} fn + * The function to run every `x` seconds. + * + * @param {number} interval + * Execute the specified function every `x` milliseconds. + * + * @return {number} + * Returns an id that can be used to identify the interval. It can also be be used in + * {@link Component#clearInterval} to clear the interval. + * + * @listens Component#dispose + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval} + */ + + + Component.prototype.setInterval = function setInterval(fn, interval) { + fn = Fn.bind(this, fn); + + var intervalId = _window2['default'].setInterval(fn, interval); + + var disposeFn = function disposeFn() { + this.clearInterval(intervalId); + }; + + disposeFn.guid = 'vjs-interval-' + intervalId; + + this.on('dispose', disposeFn); + + return intervalId; + }; + + /** + * Clears an interval that gets created via `window.setInterval` or + * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval} + * use this function instead of `window.clearInterval`. If you don't your dispose + * listener will not get cleaned up until {@link Component#dispose}! + * + * @param {number} intervalId + * The id of the interval to clear. The return value of + * {@link Component#setInterval} or `window.setInterval`. + * + * @return {number} + * Returns the interval id that was cleared. + * + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval} + */ + + + Component.prototype.clearInterval = function clearInterval(intervalId) { + _window2['default'].clearInterval(intervalId); + + var disposeFn = function disposeFn() {}; + + disposeFn.guid = 'vjs-interval-' + intervalId; + + this.off('dispose', disposeFn); + + return intervalId; + }; + + /** + * Queues up a callback to be passed to requestAnimationFrame (rAF), but + * with a few extra bonuses: + * + * - Supports browsers that do not support rAF by falling back to + * {@link Component#setTimeout}. + * + * - The callback is turned into a {@link Component~GenericCallback} (i.e. + * bound to the component). + * + * - Automatic cancellation of the rAF callback is handled if the component + * is disposed before it is called. + * + * @param {Component~GenericCallback} fn + * A function that will be bound to this component and executed just + * before the browser's next repaint. + * + * @return {number} + * Returns an rAF ID that gets used to identify the timeout. It can + * also be used in {@link Component#cancelAnimationFrame} to cancel + * the animation frame callback. + * + * @listens Component#dispose + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame} + */ + + + Component.prototype.requestAnimationFrame = function requestAnimationFrame(fn) { + var _this2 = this; + + if (this.supportsRaf_) { + fn = Fn.bind(this, fn); + + var id = _window2['default'].requestAnimationFrame(fn); + var disposeFn = function disposeFn() { + return _this2.cancelAnimationFrame(id); + }; + + disposeFn.guid = 'vjs-raf-' + id; + this.on('dispose', disposeFn); + + return id; + } + + // Fall back to using a timer. + return this.setTimeout(fn, 1000 / 60); + }; + + /** + * Cancels a queued callback passed to {@link Component#requestAnimationFrame} + * (rAF). + * + * If you queue an rAF callback via {@link Component#requestAnimationFrame}, + * use this function instead of `window.cancelAnimationFrame`. If you don't, + * your dispose listener will not get cleaned up until {@link Component#dispose}! + * + * @param {number} id + * The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}. + * + * @return {number} + * Returns the rAF ID that was cleared. + * + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame} + */ + + + Component.prototype.cancelAnimationFrame = function cancelAnimationFrame(id) { + if (this.supportsRaf_) { + _window2['default'].cancelAnimationFrame(id); + + var disposeFn = function disposeFn() {}; + + disposeFn.guid = 'vjs-raf-' + id; + + this.off('dispose', disposeFn); + + return id; + } + + // Fall back to using a timer. + return this.clearTimeout(id); + }; + + /** + * Register a `Component` with `videojs` given the name and the component. + * + * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s + * should be registered using {@link Tech.registerTech} or + * {@link videojs:videojs.registerTech}. + * + * > NOTE: This function can also be seen on videojs as + * {@link videojs:videojs.registerComponent}. + * + * @param {string} name + * The name of the `Component` to register. + * + * @param {Component} ComponentToRegister + * The `Component` class to register. + * + * @return {Component} + * The `Component` that was registered. + */ + + + Component.registerComponent = function registerComponent(name, ComponentToRegister) { + if (typeof name !== 'string' || !name) { + throw new Error('Illegal component name, "' + name + '"; must be a non-empty string.'); + } + + var Tech = Component.getComponent('Tech'); + + // We need to make sure this check is only done if Tech has been registered. + var isTech = Tech && Tech.isTech(ComponentToRegister); + var isComp = Component === ComponentToRegister || Component.prototype.isPrototypeOf(ComponentToRegister.prototype); + + if (isTech || !isComp) { + var reason = void 0; + + if (isTech) { + reason = 'techs must be registered using Tech.registerTech()'; + } else { + reason = 'must be a Component subclass'; + } + + throw new Error('Illegal component, "' + name + '"; ' + reason + '.'); + } + + name = (0, _toTitleCase2['default'])(name); + + if (!Component.components_) { + Component.components_ = {}; + } + + var Player = Component.getComponent('Player'); + + if (name === 'Player' && Player && Player.players) { + var players = Player.players; + var playerNames = Object.keys(players); + + // If we have players that were disposed, then their name will still be + // in Players.players. So, we must loop through and verify that the value + // for each item is not null. This allows registration of the Player component + // after all players have been disposed or before any were created. + if (players && playerNames.length > 0 && playerNames.map(function (pname) { + return players[pname]; + }).every(Boolean)) { + throw new Error('Can not register Player component after player has been created.'); + } + } + + Component.components_[name] = ComponentToRegister; + + return ComponentToRegister; + }; + + /** + * Get a `Component` based on the name it was registered with. + * + * @param {string} name + * The Name of the component to get. + * + * @return {Component} + * The `Component` that got registered under the given name. + * + * @deprecated In `videojs` 6 this will not return `Component`s that were not + * registered using {@link Component.registerComponent}. Currently we + * check the global `videojs` object for a `Component` name and + * return that if it exists. + */ + + + Component.getComponent = function getComponent(name) { + if (!name) { + return; + } + + name = (0, _toTitleCase2['default'])(name); + + if (Component.components_ && Component.components_[name]) { + return Component.components_[name]; + } + }; + + return Component; +}(); + +/** + * Whether or not this component supports `requestAnimationFrame`. + * + * This is exposed primarily for testing purposes. + * + * @private + * @type {Boolean} + */ + + +Component.prototype.supportsRaf_ = typeof _window2['default'].requestAnimationFrame === 'function' && typeof _window2['default'].cancelAnimationFrame === 'function'; + +Component.registerComponent('Component', Component); + +exports['default'] = Component; + +},{"100":100,"53":53,"54":54,"84":84,"85":85,"88":88,"90":90,"91":91,"92":92,"96":96}],6:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _trackButton = _dereq_(38); + +var _trackButton2 = _interopRequireDefault(_trackButton); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _audioTrackMenuItem = _dereq_(7); + +var _audioTrackMenuItem2 = _interopRequireDefault(_audioTrackMenuItem); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file audio-track-button.js + */ + + +/** + * The base class for buttons that toggle specific {@link AudioTrack} types. + * + * @extends TrackButton + */ +var AudioTrackButton = function (_TrackButton) { + _inherits(AudioTrackButton, _TrackButton); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options={}] + * The key/value store of player options. + */ + function AudioTrackButton(player) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + _classCallCheck(this, AudioTrackButton); + + options.tracks = player.audioTracks(); + + return _possibleConstructorReturn(this, _TrackButton.call(this, player, options)); + } + + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ + + + AudioTrackButton.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-audio-button ' + _TrackButton.prototype.buildCSSClass.call(this); + }; + + AudioTrackButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { + return 'vjs-audio-button ' + _TrackButton.prototype.buildWrapperCSSClass.call(this); + }; + + /** + * Create a menu item for each audio track + * + * @param {AudioTrackMenuItem[]} [items=[]] + * An array of existing menu items to use. + * + * @return {AudioTrackMenuItem[]} + * An array of menu items + */ + + + AudioTrackButton.prototype.createItems = function createItems() { + var items = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + + // if there's only one audio track, there no point in showing it + this.hideThreshold_ = 1; + + var tracks = this.player_.audioTracks(); + + for (var i = 0; i < tracks.length; i++) { + var track = tracks[i]; + + items.push(new _audioTrackMenuItem2['default'](this.player_, { + track: track, + // MenuItem is selectable + selectable: true + })); + } + + return items; + }; + + return AudioTrackButton; +}(_trackButton2['default']); + +/** + * The text that should display over the `AudioTrackButton`s controls. Added for localization. + * + * @type {string} + * @private + */ + + +AudioTrackButton.prototype.controlText_ = 'Audio Track'; +_component2['default'].registerComponent('AudioTrackButton', AudioTrackButton); +exports['default'] = AudioTrackButton; + +},{"38":38,"5":5,"7":7}],7:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _menuItem = _dereq_(51); + +var _menuItem2 = _interopRequireDefault(_menuItem); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _fn = _dereq_(88); + +var Fn = _interopRequireWildcard(_fn); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file audio-track-menu-item.js + */ + + +/** + * An {@link AudioTrack} {@link MenuItem} + * + * @extends MenuItem + */ +var AudioTrackMenuItem = function (_MenuItem) { + _inherits(AudioTrackMenuItem, _MenuItem); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function AudioTrackMenuItem(player, options) { + _classCallCheck(this, AudioTrackMenuItem); + + var track = options.track; + var tracks = player.audioTracks(); + + // Modify options for parent MenuItem class's init. + options.label = track.label || track.language || 'Unknown'; + options.selected = track.enabled; + + var _this = _possibleConstructorReturn(this, _MenuItem.call(this, player, options)); + + _this.track = track; + + var changeHandler = Fn.bind(_this, _this.handleTracksChange); + + tracks.addEventListener('change', changeHandler); + _this.on('dispose', function () { + tracks.removeEventListener('change', changeHandler); + }); + return _this; + } + + /** + * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent} + * for more detailed information on what a click can be. + * + * @param {EventTarget~Event} [event] + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click + */ + + + AudioTrackMenuItem.prototype.handleClick = function handleClick(event) { + var tracks = this.player_.audioTracks(); + + _MenuItem.prototype.handleClick.call(this, event); + + for (var i = 0; i < tracks.length; i++) { + var track = tracks[i]; + + track.enabled = track === this.track; + } + }; + + /** + * Handle any {@link AudioTrack} change. + * + * @param {EventTarget~Event} [event] + * The {@link AudioTrackList#change} event that caused this to run. + * + * @listens AudioTrackList#change + */ + + + AudioTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) { + this.selected(this.track.enabled); + }; + + return AudioTrackMenuItem; +}(_menuItem2['default']); + +_component2['default'].registerComponent('AudioTrackMenuItem', AudioTrackMenuItem); +exports['default'] = AudioTrackMenuItem; + +},{"5":5,"51":51,"88":88}],8:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +_dereq_(12); + +_dereq_(34); + +_dereq_(35); + +_dereq_(37); + +_dereq_(36); + +_dereq_(10); + +_dereq_(18); + +_dereq_(9); + +_dereq_(43); + +_dereq_(25); + +_dereq_(27); + +_dereq_(31); + +_dereq_(24); + +_dereq_(29); + +_dereq_(6); + +_dereq_(13); + +_dereq_(21); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file control-bar.js + */ + + +// Required children + + +/** + * Container of main controls. + * + * @extends Component + */ +var ControlBar = function (_Component) { + _inherits(ControlBar, _Component); + + function ControlBar() { + _classCallCheck(this, ControlBar); + + return _possibleConstructorReturn(this, _Component.apply(this, arguments)); + } + + /** + * Create the `Component`'s DOM element + * + * @return {Element} + * The element that was created. + */ + ControlBar.prototype.createEl = function createEl() { + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-control-bar', + dir: 'ltr' + }, { + // The control bar is a group, but we don't aria-label it to avoid + // over-announcing by JAWS + role: 'group' + }); + }; + + return ControlBar; +}(_component2['default']); + +/** + * Default options for `ControlBar` + * + * @type {Object} + * @private + */ + + +ControlBar.prototype.options_ = { + children: ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'fullscreenToggle'] +}; + +_component2['default'].registerComponent('ControlBar', ControlBar); +exports['default'] = ControlBar; + +},{"10":10,"12":12,"13":13,"18":18,"21":21,"24":24,"25":25,"27":27,"29":29,"31":31,"34":34,"35":35,"36":36,"37":37,"43":43,"5":5,"6":6,"9":9}],9:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _button = _dereq_(2); + +var _button2 = _interopRequireDefault(_button); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file fullscreen-toggle.js + */ + + +/** + * Toggle fullscreen video + * + * @extends Button + */ +var FullscreenToggle = function (_Button) { + _inherits(FullscreenToggle, _Button); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function FullscreenToggle(player, options) { + _classCallCheck(this, FullscreenToggle); + + var _this = _possibleConstructorReturn(this, _Button.call(this, player, options)); + + _this.on(player, 'fullscreenchange', _this.handleFullscreenChange); + return _this; + } + + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ + + + FullscreenToggle.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-fullscreen-control ' + _Button.prototype.buildCSSClass.call(this); + }; + + /** + * Handles fullscreenchange on the player and change control text accordingly. + * + * @param {EventTarget~Event} [event] + * The {@link Player#fullscreenchange} event that caused this function to be + * called. + * + * @listens Player#fullscreenchange + */ + + + FullscreenToggle.prototype.handleFullscreenChange = function handleFullscreenChange(event) { + if (this.player_.isFullscreen()) { + this.controlText('Non-Fullscreen'); + } else { + this.controlText('Fullscreen'); + } + }; + + /** + * This gets called when an `FullscreenToggle` is "clicked". See + * {@link ClickableComponent} for more detailed information on what a click can be. + * + * @param {EventTarget~Event} [event] + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click + */ + + + FullscreenToggle.prototype.handleClick = function handleClick(event) { + if (!this.player_.isFullscreen()) { + this.player_.requestFullscreen(); + } else { + this.player_.exitFullscreen(); + } + }; + + return FullscreenToggle; +}(_button2['default']); + +/** + * The text that should display over the `FullscreenToggle`s controls. Added for localization. + * + * @type {string} + * @private + */ + + +FullscreenToggle.prototype.controlText_ = 'Fullscreen'; + +_component2['default'].registerComponent('FullscreenToggle', FullscreenToggle); +exports['default'] = FullscreenToggle; + +},{"2":2,"5":5}],10:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _dom = _dereq_(85); + +var Dom = _interopRequireWildcard(_dom); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file live-display.js + */ + + +// TODO - Future make it click to snap to live + +/** + * Displays the live indicator when duration is Infinity. + * + * @extends Component + */ +var LiveDisplay = function (_Component) { + _inherits(LiveDisplay, _Component); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function LiveDisplay(player, options) { + _classCallCheck(this, LiveDisplay); + + var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); + + _this.updateShowing(); + _this.on(_this.player(), 'durationchange', _this.updateShowing); + return _this; + } + + /** + * Create the `Component`'s DOM element + * + * @return {Element} + * The element that was created. + */ + + + LiveDisplay.prototype.createEl = function createEl() { + var el = _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-live-control vjs-control' + }); + + this.contentEl_ = Dom.createEl('div', { + className: 'vjs-live-display', + innerHTML: '' + this.localize('Stream Type') + '' + this.localize('LIVE') + }, { + 'aria-live': 'off' + }); + + el.appendChild(this.contentEl_); + return el; + }; + + /** + * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide + * it accordingly + * + * @param {EventTarget~Event} [event] + * The {@link Player#durationchange} event that caused this function to run. + * + * @listens Player#durationchange + */ + + + LiveDisplay.prototype.updateShowing = function updateShowing(event) { + if (this.player().duration() === Infinity) { + this.show(); + } else { + this.hide(); + } + }; + + return LiveDisplay; +}(_component2['default']); + +_component2['default'].registerComponent('LiveDisplay', LiveDisplay); +exports['default'] = LiveDisplay; + +},{"5":5,"85":85}],11:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _button = _dereq_(2); + +var _button2 = _interopRequireDefault(_button); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _dom = _dereq_(85); + +var Dom = _interopRequireWildcard(_dom); + +var _checkVolumeSupport = _dereq_(39); + +var _checkVolumeSupport2 = _interopRequireDefault(_checkVolumeSupport); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file mute-toggle.js + */ + + +/** + * A button component for muting the audio. + * + * @extends Button + */ +var MuteToggle = function (_Button) { + _inherits(MuteToggle, _Button); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function MuteToggle(player, options) { + _classCallCheck(this, MuteToggle); + + // hide this control if volume support is missing + var _this = _possibleConstructorReturn(this, _Button.call(this, player, options)); + + (0, _checkVolumeSupport2['default'])(_this, player); + + _this.on(player, ['loadstart', 'volumechange'], _this.update); + return _this; + } + + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ + + + MuteToggle.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-mute-control ' + _Button.prototype.buildCSSClass.call(this); + }; + + /** + * This gets called when an `MuteToggle` is "clicked". See + * {@link ClickableComponent} for more detailed information on what a click can be. + * + * @param {EventTarget~Event} [event] + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click + */ + + + MuteToggle.prototype.handleClick = function handleClick(event) { + var vol = this.player_.volume(); + var lastVolume = this.player_.lastVolume_(); + + if (vol === 0) { + var volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume; + + this.player_.volume(volumeToSet); + this.player_.muted(false); + } else { + this.player_.muted(this.player_.muted() ? false : true); + } + }; + + /** + * Update the `MuteToggle` button based on the state of `volume` and `muted` + * on the player. + * + * @param {EventTarget~Event} [event] + * The {@link Player#loadstart} event if this function was called + * through an event. + * + * @listens Player#loadstart + * @listens Player#volumechange + */ + + + MuteToggle.prototype.update = function update(event) { + this.updateIcon_(); + this.updateControlText_(); + }; + + /** + * Update the appearance of the `MuteToggle` icon. + * + * Possible states (given `level` variable below): + * - 0: crossed out + * - 1: zero bars of volume + * - 2: one bar of volume + * - 3: two bars of volume + * + * @private + */ + + + MuteToggle.prototype.updateIcon_ = function updateIcon_() { + var vol = this.player_.volume(); + var level = 3; + + if (vol === 0 || this.player_.muted()) { + level = 0; + } else if (vol < 0.33) { + level = 1; + } else if (vol < 0.67) { + level = 2; + } + + // TODO improve muted icon classes + for (var i = 0; i < 4; i++) { + Dom.removeClass(this.el_, 'vjs-vol-' + i); + } + Dom.addClass(this.el_, 'vjs-vol-' + level); + }; + + /** + * If `muted` has changed on the player, update the control text + * (`title` attribute on `vjs-mute-control` element and content of + * `vjs-control-text` element). + * + * @private + */ + + + MuteToggle.prototype.updateControlText_ = function updateControlText_() { + var soundOff = this.player_.muted() || this.player_.volume() === 0; + var text = soundOff ? 'Unmute' : 'Mute'; + + if (this.controlText() !== text) { + this.controlText(text); + } + }; + + return MuteToggle; +}(_button2['default']); + +/** + * The text that should display over the `MuteToggle`s controls. Added for localization. + * + * @type {string} + * @private + */ + + +MuteToggle.prototype.controlText_ = 'Mute'; + +_component2['default'].registerComponent('MuteToggle', MuteToggle); +exports['default'] = MuteToggle; + +},{"2":2,"39":39,"5":5,"85":85}],12:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _button = _dereq_(2); + +var _button2 = _interopRequireDefault(_button); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file play-toggle.js + */ + + +/** + * Button to toggle between play and pause. + * + * @extends Button + */ +var PlayToggle = function (_Button) { + _inherits(PlayToggle, _Button); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function PlayToggle(player, options) { + _classCallCheck(this, PlayToggle); + + var _this = _possibleConstructorReturn(this, _Button.call(this, player, options)); + + _this.on(player, 'play', _this.handlePlay); + _this.on(player, 'pause', _this.handlePause); + _this.on(player, 'ended', _this.handleEnded); + return _this; + } + + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ + + + PlayToggle.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-play-control ' + _Button.prototype.buildCSSClass.call(this); + }; + + /** + * This gets called when an `PlayToggle` is "clicked". See + * {@link ClickableComponent} for more detailed information on what a click can be. + * + * @param {EventTarget~Event} [event] + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click + */ + + + PlayToggle.prototype.handleClick = function handleClick(event) { + if (this.player_.paused()) { + this.player_.play(); + } else { + this.player_.pause(); + } + }; + + /** + * Add the vjs-playing class to the element so it can change appearance. + * + * @param {EventTarget~Event} [event] + * The event that caused this function to run. + * + * @listens Player#play + */ + + + PlayToggle.prototype.handlePlay = function handlePlay(event) { + this.removeClass('vjs-ended'); + this.removeClass('vjs-paused'); + this.addClass('vjs-playing'); + // change the button text to "Pause" + this.controlText('Pause'); + }; + + /** + * Add the vjs-paused class to the element so it can change appearance. + * + * @param {EventTarget~Event} [event] + * The event that caused this function to run. + * + * @listens Player#pause + */ + + + PlayToggle.prototype.handlePause = function handlePause(event) { + this.removeClass('vjs-playing'); + this.addClass('vjs-paused'); + // change the button text to "Play" + this.controlText('Play'); + }; + + /** + * Add the vjs-ended class to the element so it can change appearance + * + */ + + + PlayToggle.prototype.handleEnded = function handleEnded(event) { + this.removeClass('vjs-playing'); + this.addClass('vjs-ended'); + // change the button text to "Replay" + this.controlText('Replay'); + }; + + return PlayToggle; +}(_button2['default']); + +/** + * The text that should display over the `PlayToggle`s controls. Added for localization. + * + * @type {string} + * @private + */ + + +PlayToggle.prototype.controlText_ = 'Play'; + +_component2['default'].registerComponent('PlayToggle', PlayToggle); +exports['default'] = PlayToggle; + +},{"2":2,"5":5}],13:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _menuButton = _dereq_(50); + +var _menuButton2 = _interopRequireDefault(_menuButton); + +var _menu = _dereq_(52); + +var _menu2 = _interopRequireDefault(_menu); + +var _playbackRateMenuItem = _dereq_(14); + +var _playbackRateMenuItem2 = _interopRequireDefault(_playbackRateMenuItem); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _dom = _dereq_(85); + +var Dom = _interopRequireWildcard(_dom); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file playback-rate-menu-button.js + */ + + +/** + * The component for controlling the playback rate. + * + * @extends MenuButton + */ +var PlaybackRateMenuButton = function (_MenuButton) { + _inherits(PlaybackRateMenuButton, _MenuButton); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function PlaybackRateMenuButton(player, options) { + _classCallCheck(this, PlaybackRateMenuButton); + + var _this = _possibleConstructorReturn(this, _MenuButton.call(this, player, options)); + + _this.updateVisibility(); + _this.updateLabel(); + + _this.on(player, 'loadstart', _this.updateVisibility); + _this.on(player, 'ratechange', _this.updateLabel); + return _this; + } + + /** + * Create the `Component`'s DOM element + * + * @return {Element} + * The element that was created. + */ + + + PlaybackRateMenuButton.prototype.createEl = function createEl() { + var el = _MenuButton.prototype.createEl.call(this); + + this.labelEl_ = Dom.createEl('div', { + className: 'vjs-playback-rate-value', + innerHTML: 1.0 + }); + + el.appendChild(this.labelEl_); + + return el; + }; + + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ + + + PlaybackRateMenuButton.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-playback-rate ' + _MenuButton.prototype.buildCSSClass.call(this); + }; + + PlaybackRateMenuButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { + return 'vjs-playback-rate ' + _MenuButton.prototype.buildWrapperCSSClass.call(this); + }; + + /** + * Create the playback rate menu + * + * @return {Menu} + * Menu object populated with {@link PlaybackRateMenuItem}s + */ + + + PlaybackRateMenuButton.prototype.createMenu = function createMenu() { + var menu = new _menu2['default'](this.player()); + var rates = this.playbackRates(); + + if (rates) { + for (var i = rates.length - 1; i >= 0; i--) { + menu.addChild(new _playbackRateMenuItem2['default'](this.player(), { rate: rates[i] + 'x' })); + } + } + + return menu; + }; + + /** + * Updates ARIA accessibility attributes + */ + + + PlaybackRateMenuButton.prototype.updateARIAAttributes = function updateARIAAttributes() { + // Current playback rate + this.el().setAttribute('aria-valuenow', this.player().playbackRate()); + }; + + /** + * This gets called when an `PlaybackRateMenuButton` is "clicked". See + * {@link ClickableComponent} for more detailed information on what a click can be. + * + * @param {EventTarget~Event} [event] + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click + */ + + + PlaybackRateMenuButton.prototype.handleClick = function handleClick(event) { + // select next rate option + var currentRate = this.player().playbackRate(); + var rates = this.playbackRates(); + + // 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); + }; + + /** + * Get possible playback rates + * + * @return {Array} + * All possible playback rates + */ + + + PlaybackRateMenuButton.prototype.playbackRates = function playbackRates() { + return this.options_.playbackRates || this.options_.playerOptions && this.options_.playerOptions.playbackRates; + }; + + /** + * Get whether playback rates is supported by the tech + * and an array of playback rates exists + * + * @return {boolean} + * Whether changing playback rate is supported + */ + + + PlaybackRateMenuButton.prototype.playbackRateSupported = function playbackRateSupported() { + return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0; + }; + + /** + * Hide playback rate controls when they're no playback rate options to select + * + * @param {EventTarget~Event} [event] + * The event that caused this function to run. + * + * @listens Player#loadstart + */ + + + PlaybackRateMenuButton.prototype.updateVisibility = function updateVisibility(event) { + if (this.playbackRateSupported()) { + this.removeClass('vjs-hidden'); + } else { + this.addClass('vjs-hidden'); + } + }; + + /** + * Update button label when rate changed + * + * @param {EventTarget~Event} [event] + * The event that caused this function to run. + * + * @listens Player#ratechange + */ + + + PlaybackRateMenuButton.prototype.updateLabel = function updateLabel(event) { + if (this.playbackRateSupported()) { + this.labelEl_.innerHTML = this.player().playbackRate() + 'x'; + } + }; + + return PlaybackRateMenuButton; +}(_menuButton2['default']); + +/** + * The text that should display over the `FullscreenToggle`s controls. Added for localization. + * + * @type {string} + * @private + */ + + +PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate'; + +_component2['default'].registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton); +exports['default'] = PlaybackRateMenuButton; + +},{"14":14,"5":5,"50":50,"52":52,"85":85}],14:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _menuItem = _dereq_(51); + +var _menuItem2 = _interopRequireDefault(_menuItem); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file playback-rate-menu-item.js + */ + + +/** + * The specific menu item type for selecting a playback rate. + * + * @extends MenuItem + */ +var PlaybackRateMenuItem = function (_MenuItem) { + _inherits(PlaybackRateMenuItem, _MenuItem); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function PlaybackRateMenuItem(player, options) { + _classCallCheck(this, PlaybackRateMenuItem); + + var label = options.rate; + var rate = parseFloat(label, 10); + + // Modify options for parent MenuItem class's init. + options.label = label; + options.selected = rate === 1; + options.selectable = true; + + var _this = _possibleConstructorReturn(this, _MenuItem.call(this, player, options)); + + _this.label = label; + _this.rate = rate; + + _this.on(player, 'ratechange', _this.update); + return _this; + } + + /** + * This gets called when an `PlaybackRateMenuItem` is "clicked". See + * {@link ClickableComponent} for more detailed information on what a click can be. + * + * @param {EventTarget~Event} [event] + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click + */ + + + PlaybackRateMenuItem.prototype.handleClick = function handleClick(event) { + _MenuItem.prototype.handleClick.call(this); + this.player().playbackRate(this.rate); + }; + + /** + * Update the PlaybackRateMenuItem when the playbackrate changes. + * + * @param {EventTarget~Event} [event] + * The `ratechange` event that caused this function to run. + * + * @listens Player#ratechange + */ + + + PlaybackRateMenuItem.prototype.update = function update(event) { + this.selected(this.player().playbackRate() === this.rate); + }; + + return PlaybackRateMenuItem; +}(_menuItem2['default']); + +/** + * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization. + * + * @type {string} + * @private + */ + + +PlaybackRateMenuItem.prototype.contentElType = 'button'; + +_component2['default'].registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem); +exports['default'] = PlaybackRateMenuItem; + +},{"5":5,"51":51}],15:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _dom = _dereq_(85); + +var Dom = _interopRequireWildcard(_dom); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file load-progress-bar.js + */ + + +/** + * Shows loading progress + * + * @extends Component + */ +var LoadProgressBar = function (_Component) { + _inherits(LoadProgressBar, _Component); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function LoadProgressBar(player, options) { + _classCallCheck(this, LoadProgressBar); + + var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); + + _this.partEls_ = []; + _this.on(player, 'progress', _this.update); + return _this; + } + + /** + * Create the `Component`'s DOM element + * + * @return {Element} + * The element that was created. + */ + + + LoadProgressBar.prototype.createEl = function createEl() { + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-load-progress', + innerHTML: '' + this.localize('Loaded') + ': 0%' + }); + }; + + /** + * Update progress bar + * + * @param {EventTarget~Event} [event] + * The `progress` event that caused this function to run. + * + * @listens Player#progress + */ + + + LoadProgressBar.prototype.update = function update(event) { + var buffered = this.player_.buffered(); + var duration = this.player_.duration(); + var bufferedEnd = this.player_.bufferedEnd(); + var children = this.partEls_; + + // get the percent width of a time compared to the total end + var percentify = function percentify(time, end) { + // no NaN + var percent = time / end || 0; + + return (percent >= 1 ? 1 : percent) * 100 + '%'; + }; + + // update the width of the progress bar + this.el_.style.width = percentify(bufferedEnd, duration); + + // 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]; + + if (!part) { + part = this.el_.appendChild(Dom.createEl()); + children[i] = part; + } + + // 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]); + } + children.length = buffered.length; + }; + + return LoadProgressBar; +}(_component2['default']); + +_component2['default'].registerComponent('LoadProgressBar', LoadProgressBar); +exports['default'] = LoadProgressBar; + +},{"5":5,"85":85}],16:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _fn = _dereq_(88); + +var Fn = _interopRequireWildcard(_fn); + +var _formatTime = _dereq_(89); + +var _formatTime2 = _interopRequireDefault(_formatTime); + +_dereq_(20); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file mouse-time-display.js + */ + + +/** + * The {@link MouseTimeDisplay} component tracks mouse movement over the + * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip} + * indicating the time which is represented by a given point in the + * {@link ProgressControl}. + * + * @extends Component + */ +var MouseTimeDisplay = function (_Component) { + _inherits(MouseTimeDisplay, _Component); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The {@link Player} that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function MouseTimeDisplay(player, options) { + _classCallCheck(this, MouseTimeDisplay); + + var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); + + _this.update = Fn.throttle(Fn.bind(_this, _this.update), 25); + return _this; + } + + /** + * Create the DOM element for this class. + * + * @return {Element} + * The element that was created. + */ + + + MouseTimeDisplay.prototype.createEl = function createEl() { + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-mouse-display' + }); + }; + + /** + * Enqueues updates to its own DOM as well as the DOM of its + * {@link TimeTooltip} child. + * + * @param {Object} seekBarRect + * The `ClientRect` for the {@link SeekBar} element. + * + * @param {number} seekBarPoint + * A number from 0 to 1, representing a horizontal reference point + * from the left edge of the {@link SeekBar} + */ + + + MouseTimeDisplay.prototype.update = function update(seekBarRect, seekBarPoint) { + var _this2 = this; + + // If there is an existing rAF ID, cancel it so we don't over-queue. + if (this.rafId_) { + this.cancelAnimationFrame(this.rafId_); + } + + this.rafId_ = this.requestAnimationFrame(function () { + var duration = _this2.player_.duration(); + var content = (0, _formatTime2['default'])(seekBarPoint * duration, duration); + + _this2.el_.style.left = seekBarRect.width * seekBarPoint + 'px'; + _this2.getChild('timeTooltip').update(seekBarRect, seekBarPoint, content); + }); + }; + + return MouseTimeDisplay; +}(_component2['default']); + +/** + * Default options for `MouseTimeDisplay` + * + * @type {Object} + * @private + */ + + +MouseTimeDisplay.prototype.options_ = { + children: ['timeTooltip'] +}; + +_component2['default'].registerComponent('MouseTimeDisplay', MouseTimeDisplay); +exports['default'] = MouseTimeDisplay; + +},{"20":20,"5":5,"88":88,"89":89}],17:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _browser = _dereq_(81); + +var _formatTime = _dereq_(89); + +var _formatTime2 = _interopRequireDefault(_formatTime); + +_dereq_(20); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file play-progress-bar.js + */ + + +/** + * Used by {@link SeekBar} to display media playback progress as part of the + * {@link ProgressControl}. + * + * @extends Component + */ +var PlayProgressBar = function (_Component) { + _inherits(PlayProgressBar, _Component); + + function PlayProgressBar() { + _classCallCheck(this, PlayProgressBar); + + return _possibleConstructorReturn(this, _Component.apply(this, arguments)); + } + + /** + * Create the the DOM element for this class. + * + * @return {Element} + * The element that was created. + */ + PlayProgressBar.prototype.createEl = function createEl() { + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-play-progress vjs-slider-bar', + innerHTML: '' + this.localize('Progress') + ': 0%' + }); + }; + + /** + * Enqueues updates to its own DOM as well as the DOM of its + * {@link TimeTooltip} child. + * + * @param {Object} seekBarRect + * The `ClientRect` for the {@link SeekBar} element. + * + * @param {number} seekBarPoint + * A number from 0 to 1, representing a horizontal reference point + * from the left edge of the {@link SeekBar} + */ + + + PlayProgressBar.prototype.update = function update(seekBarRect, seekBarPoint) { + var _this2 = this; + + // If there is an existing rAF ID, cancel it so we don't over-queue. + if (this.rafId_) { + this.cancelAnimationFrame(this.rafId_); + } + + this.rafId_ = this.requestAnimationFrame(function () { + var time = _this2.player_.scrubbing() ? _this2.player_.getCache().currentTime : _this2.player_.currentTime(); + + var content = (0, _formatTime2['default'])(time, _this2.player_.duration()); + var timeTooltip = _this2.getChild('timeTooltip'); + + if (timeTooltip) { + timeTooltip.update(seekBarRect, seekBarPoint, content); + } + }); + }; + + return PlayProgressBar; +}(_component2['default']); + +/** + * Default options for {@link PlayProgressBar}. + * + * @type {Object} + * @private + */ + + +PlayProgressBar.prototype.options_ = { + children: [] +}; + +// Time tooltips should not be added to a player on mobile devices or IE8 +if ((!_browser.IE_VERSION || _browser.IE_VERSION > 8) && !_browser.IS_IOS && !_browser.IS_ANDROID) { + PlayProgressBar.prototype.options_.children.push('timeTooltip'); +} + +_component2['default'].registerComponent('PlayProgressBar', PlayProgressBar); +exports['default'] = PlayProgressBar; + +},{"20":20,"5":5,"81":81,"89":89}],18:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _dom = _dereq_(85); + +var Dom = _interopRequireWildcard(_dom); + +var _fn = _dereq_(88); + +_dereq_(19); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file progress-control.js + */ + + +/** + * The Progress Control component contains the seek bar, load progress, + * and play progress. + * + * @extends Component + */ +var ProgressControl = function (_Component) { + _inherits(ProgressControl, _Component); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function ProgressControl(player, options) { + _classCallCheck(this, ProgressControl); + + var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); + + _this.handleMouseMove = (0, _fn.throttle)((0, _fn.bind)(_this, _this.handleMouseMove), 25); + _this.on(_this.el_, 'mousemove', _this.handleMouseMove); + + _this.throttledHandleMouseSeek = (0, _fn.throttle)((0, _fn.bind)(_this, _this.handleMouseSeek), 25); + _this.on(['mousedown', 'touchstart'], _this.handleMouseDown); + return _this; + } + + /** + * Create the `Component`'s DOM element + * + * @return {Element} + * The element that was created. + */ + + + ProgressControl.prototype.createEl = function createEl() { + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-progress-control vjs-control' + }); + }; + + /** + * When the mouse moves over the `ProgressControl`, the pointer position + * gets passed down to the `MouseTimeDisplay` component. + * + * @param {EventTarget~Event} event + * The `mousemove` event that caused this function to run. + * + * @listen mousemove + */ + + + ProgressControl.prototype.handleMouseMove = function handleMouseMove(event) { + var seekBar = this.getChild('seekBar'); + var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay'); + var seekBarEl = seekBar.el(); + var seekBarRect = Dom.getBoundingClientRect(seekBarEl); + var seekBarPoint = Dom.getPointerPosition(seekBarEl, event).x; + + // The default skin has a gap on either side of the `SeekBar`. This means + // that it's possible to trigger this behavior outside the boundaries of + // the `SeekBar`. This ensures we stay within it at all times. + if (seekBarPoint > 1) { + seekBarPoint = 1; + } else if (seekBarPoint < 0) { + seekBarPoint = 0; + } + + if (mouseTimeDisplay) { + mouseTimeDisplay.update(seekBarRect, seekBarPoint); + } + }; + + /** + * A throttled version of the {@link ProgressControl#handleMouseSeek} listener. + * + * @method ProgressControl#throttledHandleMouseSeek + * @param {EventTarget~Event} event + * The `mousemove` event that caused this function to run. + * + * @listen mousemove + * @listen touchmove + */ + + /** + * Handle `mousemove` or `touchmove` events on the `ProgressControl`. + * + * @param {EventTarget~Event} event + * `mousedown` or `touchstart` event that triggered this function + * + * @listens mousemove + * @listens touchmove + */ + + + ProgressControl.prototype.handleMouseSeek = function handleMouseSeek(event) { + var seekBar = this.getChild('seekBar'); + + seekBar.handleMouseMove(event); + }; + + /** + * Handle `mousedown` or `touchstart` events on the `ProgressControl`. + * + * @param {EventTarget~Event} event + * `mousedown` or `touchstart` event that triggered this function + * + * @listens mousedown + * @listens touchstart + */ + + + ProgressControl.prototype.handleMouseDown = function handleMouseDown(event) { + var doc = this.el_.ownerDocument; + + this.on(doc, 'mousemove', this.throttledHandleMouseSeek); + this.on(doc, 'touchmove', this.throttledHandleMouseSeek); + this.on(doc, 'mouseup', this.handleMouseUp); + this.on(doc, 'touchend', this.handleMouseUp); + }; + + /** + * Handle `mouseup` or `touchend` events on the `ProgressControl`. + * + * @param {EventTarget~Event} event + * `mouseup` or `touchend` event that triggered this function. + * + * @listens touchend + * @listens mouseup + */ + + + ProgressControl.prototype.handleMouseUp = function handleMouseUp(event) { + var doc = this.el_.ownerDocument; + + this.off(doc, 'mousemove', this.throttledHandleMouseSeek); + this.off(doc, 'touchmove', this.throttledHandleMouseSeek); + this.off(doc, 'mouseup', this.handleMouseUp); + this.off(doc, 'touchend', this.handleMouseUp); + }; + + return ProgressControl; +}(_component2['default']); + +/** + * Default options for `ProgressControl` + * + * @type {Object} + * @private + */ + + +ProgressControl.prototype.options_ = { + children: ['seekBar'] +}; + +_component2['default'].registerComponent('ProgressControl', ProgressControl); +exports['default'] = ProgressControl; + +},{"19":19,"5":5,"85":85,"88":88}],19:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _slider = _dereq_(60); + +var _slider2 = _interopRequireDefault(_slider); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _browser = _dereq_(81); + +var _dom = _dereq_(85); + +var Dom = _interopRequireWildcard(_dom); + +var _fn = _dereq_(88); + +var Fn = _interopRequireWildcard(_fn); + +var _formatTime = _dereq_(89); + +var _formatTime2 = _interopRequireDefault(_formatTime); + +_dereq_(15); + +_dereq_(17); + +_dereq_(16); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file seek-bar.js + */ + + +// The number of seconds the `step*` functions move the timeline. +var STEP_SECONDS = 5; + +/** + * Seek bar and container for the progress bars. Uses {@link PlayProgressBar} + * as its `bar`. + * + * @extends Slider + */ + +var SeekBar = function (_Slider) { + _inherits(SeekBar, _Slider); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function SeekBar(player, options) { + _classCallCheck(this, SeekBar); + + var _this = _possibleConstructorReturn(this, _Slider.call(this, player, options)); + + _this.update = Fn.throttle(Fn.bind(_this, _this.update), 50); + _this.on(player, ['timeupdate', 'ended'], _this.update); + return _this; + } + + /** + * Create the `Component`'s DOM element + * + * @return {Element} + * The element that was created. + */ + + + SeekBar.prototype.createEl = function createEl() { + return _Slider.prototype.createEl.call(this, 'div', { + className: 'vjs-progress-holder' + }, { + 'aria-label': this.localize('Progress Bar') + }); + }; + + /** + * Update the seek bar's UI. + * + * @param {EventTarget~Event} [event] + * The `timeupdate` or `ended` event that caused this to run. + * + * @listens Player#timeupdate + * @listens Player#ended + */ + + + SeekBar.prototype.update = function update() { + var percent = _Slider.prototype.update.call(this); + var duration = this.player_.duration(); + + // Allows for smooth scrubbing, when player can't keep up. + var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); + + // machine readable value of progress bar (percentage complete) + this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2)); + + // human readable value of progress bar (time complete) + this.el_.setAttribute('aria-valuetext', this.localize('progress bar timing: currentTime={1} duration={2}', [(0, _formatTime2['default'])(time, duration), (0, _formatTime2['default'])(duration, duration)], '{1} of {2}')); + + // Update the `PlayProgressBar`. + this.bar.update(Dom.getBoundingClientRect(this.el_), percent); + + return percent; + }; + + /** + * Get the percentage of media played so far. + * + * @return {number} + * The percentage of media played so far (0 to 1). + */ + + + SeekBar.prototype.getPercent = function getPercent() { + + // Allows for smooth scrubbing, when player can't keep up. + var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); + + var percent = time / this.player_.duration(); + + return percent >= 1 ? 1 : percent; + }; + + /** + * Handle mouse down on seek bar + * + * @param {EventTarget~Event} event + * The `mousedown` event that caused this to run. + * + * @listens mousedown + */ + + + SeekBar.prototype.handleMouseDown = function handleMouseDown(event) { + this.player_.scrubbing(true); + + this.videoWasPlaying = !this.player_.paused(); + this.player_.pause(); + + _Slider.prototype.handleMouseDown.call(this, event); + }; + + /** + * Handle mouse move on seek bar + * + * @param {EventTarget~Event} event + * The `mousemove` event that caused this to run. + * + * @listens mousemove + */ + + + SeekBar.prototype.handleMouseMove = function handleMouseMove(event) { + var newTime = this.calculateDistance(event) * this.player_.duration(); + + // Don't let video end while scrubbing. + if (newTime === this.player_.duration()) { + newTime = newTime - 0.1; + } + + // Set new time (tell player to seek to new time) + this.player_.currentTime(newTime); + }; + + /** + * Handle mouse up on seek bar + * + * @param {EventTarget~Event} event + * The `mouseup` event that caused this to run. + * + * @listens mouseup + */ + + + SeekBar.prototype.handleMouseUp = function handleMouseUp(event) { + _Slider.prototype.handleMouseUp.call(this, event); + + this.player_.scrubbing(false); + if (this.videoWasPlaying) { + this.player_.play(); + } + }; + + /** + * Move more quickly fast forward for keyboard-only users + */ + + + SeekBar.prototype.stepForward = function stepForward() { + this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS); + }; + + /** + * Move more quickly rewind for keyboard-only users + */ + + + SeekBar.prototype.stepBack = function stepBack() { + this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS); + }; + + /** + * Toggles the playback state of the player + * This gets called when enter or space is used on the seekbar + * + * @param {EventTarget~Event} event + * The `keydown` event that caused this function to be called + * + */ + + + SeekBar.prototype.handleAction = function handleAction(event) { + if (this.player_.paused()) { + this.player_.play(); + } else { + this.player_.pause(); + } + }; + + /** + * Called when this SeekBar has focus and a key gets pressed down. By + * default it will call `this.handleAction` when the key is space or enter. + * + * @param {EventTarget~Event} event + * The `keydown` event that caused this function to be called. + * + * @listens keydown + */ + + + SeekBar.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.handleAction(event); + } else if (_Slider.prototype.handleKeyPress) { + + // Pass keypress handling up for unsupported keys + _Slider.prototype.handleKeyPress.call(this, event); + } + }; + + return SeekBar; +}(_slider2['default']); + +/** + * Default options for the `SeekBar` + * + * @type {Object} + * @private + */ + + +SeekBar.prototype.options_ = { + children: ['loadProgressBar', 'playProgressBar'], + barName: 'playProgressBar' +}; + +// MouseTimeDisplay tooltips should not be added to a player on mobile devices or IE8 +if ((!_browser.IE_VERSION || _browser.IE_VERSION > 8) && !_browser.IS_IOS && !_browser.IS_ANDROID) { + SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay'); +} + +/** + * Call the update event for this Slider when this event happens on the player. + * + * @type {string} + */ +SeekBar.prototype.playerEvent = 'timeupdate'; + +_component2['default'].registerComponent('SeekBar', SeekBar); +exports['default'] = SeekBar; + +},{"15":15,"16":16,"17":17,"5":5,"60":60,"81":81,"85":85,"88":88,"89":89}],20:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _dom = _dereq_(85); + +var Dom = _interopRequireWildcard(_dom); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file time-tooltip.js + */ + + +/** + * Time tooltips display a time above the progress bar. + * + * @extends Component + */ +var TimeTooltip = function (_Component) { + _inherits(TimeTooltip, _Component); + + function TimeTooltip() { + _classCallCheck(this, TimeTooltip); + + return _possibleConstructorReturn(this, _Component.apply(this, arguments)); + } + + /** + * Create the time tooltip DOM element + * + * @return {Element} + * The element that was created. + */ + TimeTooltip.prototype.createEl = function createEl() { + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-time-tooltip' + }); + }; + + /** + * Updates the position of the time tooltip relative to the `SeekBar`. + * + * @param {Object} seekBarRect + * The `ClientRect` for the {@link SeekBar} element. + * + * @param {number} seekBarPoint + * A number from 0 to 1, representing a horizontal reference point + * from the left edge of the {@link SeekBar} + */ + + + TimeTooltip.prototype.update = function update(seekBarRect, seekBarPoint, content) { + var tooltipRect = Dom.getBoundingClientRect(this.el_); + var playerRect = Dom.getBoundingClientRect(this.player_.el()); + var seekBarPointPx = seekBarRect.width * seekBarPoint; + + // do nothing if either rect isn't available + // for example, if the player isn't in the DOM for testing + if (!playerRect || !tooltipRect) { + return; + } + + // This is the space left of the `seekBarPoint` available within the bounds + // of the player. We calculate any gap between the left edge of the player + // and the left edge of the `SeekBar` and add the number of pixels in the + // `SeekBar` before hitting the `seekBarPoint` + var spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx; + + // This is the space right of the `seekBarPoint` available within the bounds + // of the player. We calculate the number of pixels from the `seekBarPoint` + // to the right edge of the `SeekBar` and add to that any gap between the + // right edge of the `SeekBar` and the player. + var spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right); + + // This is the number of pixels by which the tooltip will need to be pulled + // further to the right to center it over the `seekBarPoint`. + var pullTooltipBy = tooltipRect.width / 2; + + // Adjust the `pullTooltipBy` distance to the left or right depending on + // the results of the space calculations above. + if (spaceLeftOfPoint < pullTooltipBy) { + pullTooltipBy += pullTooltipBy - spaceLeftOfPoint; + } else if (spaceRightOfPoint < pullTooltipBy) { + pullTooltipBy = spaceRightOfPoint; + } + + // Due to the imprecision of decimal/ratio based calculations and varying + // rounding behaviors, there are cases where the spacing adjustment is off + // by a pixel or two. This adds insurance to these calculations. + if (pullTooltipBy < 0) { + pullTooltipBy = 0; + } else if (pullTooltipBy > tooltipRect.width) { + pullTooltipBy = tooltipRect.width; + } + + this.el_.style.right = '-' + pullTooltipBy + 'px'; + Dom.textContent(this.el_, content); + }; + + return TimeTooltip; +}(_component2['default']); + +_component2['default'].registerComponent('TimeTooltip', TimeTooltip); +exports['default'] = TimeTooltip; + +},{"5":5,"85":85}],21:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _spacer = _dereq_(22); + +var _spacer2 = _interopRequireDefault(_spacer); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file custom-control-spacer.js + */ + + +/** + * Spacer specifically meant to be used as an insertion point for new plugins, etc. + * + * @extends Spacer + */ +var CustomControlSpacer = function (_Spacer) { + _inherits(CustomControlSpacer, _Spacer); + + function CustomControlSpacer() { + _classCallCheck(this, CustomControlSpacer); + + return _possibleConstructorReturn(this, _Spacer.apply(this, arguments)); + } + + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ + CustomControlSpacer.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-custom-control-spacer ' + _Spacer.prototype.buildCSSClass.call(this); + }; + + /** + * Create the `Component`'s DOM element + * + * @return {Element} + * The element that was created. + */ + + + CustomControlSpacer.prototype.createEl = function createEl() { + var el = _Spacer.prototype.createEl.call(this, { + className: this.buildCSSClass() + }); + + // No-flex/table-cell mode requires there be some content + // in the cell to fill the remaining space of the table. + el.innerHTML = ' '; + return el; + }; + + return CustomControlSpacer; +}(_spacer2['default']); + +_component2['default'].registerComponent('CustomControlSpacer', CustomControlSpacer); +exports['default'] = CustomControlSpacer; + +},{"22":22,"5":5}],22:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file spacer.js + */ + + +/** + * Just an empty spacer element that can be used as an append point for plugins, etc. + * Also can be used to create space between elements when necessary. + * + * @extends Component + */ +var Spacer = function (_Component) { + _inherits(Spacer, _Component); + + function Spacer() { + _classCallCheck(this, Spacer); + + return _possibleConstructorReturn(this, _Component.apply(this, arguments)); + } + + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ + Spacer.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-spacer ' + _Component.prototype.buildCSSClass.call(this); + }; + + /** + * Create the `Component`'s DOM element + * + * @return {Element} + * The element that was created. + */ + + + Spacer.prototype.createEl = function createEl() { + return _Component.prototype.createEl.call(this, 'div', { + className: this.buildCSSClass() + }); + }; + + return Spacer; +}(_component2['default']); + +_component2['default'].registerComponent('Spacer', Spacer); + +exports['default'] = Spacer; + +},{"5":5}],23:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _textTrackMenuItem = _dereq_(33); + +var _textTrackMenuItem2 = _interopRequireDefault(_textTrackMenuItem); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file caption-settings-menu-item.js + */ + + +/** + * The menu item for caption track settings menu + * + * @extends TextTrackMenuItem + */ +var CaptionSettingsMenuItem = function (_TextTrackMenuItem) { + _inherits(CaptionSettingsMenuItem, _TextTrackMenuItem); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function CaptionSettingsMenuItem(player, options) { + _classCallCheck(this, CaptionSettingsMenuItem); + + options.track = { + player: player, + kind: options.kind, + label: options.kind + ' settings', + selectable: false, + 'default': false, + mode: 'disabled' + }; + + // CaptionSettingsMenuItem has no concept of 'selected' + options.selectable = false; + + options.name = 'CaptionSettingsMenuItem'; + + var _this = _possibleConstructorReturn(this, _TextTrackMenuItem.call(this, player, options)); + + _this.addClass('vjs-texttrack-settings'); + _this.controlText(', opens ' + options.kind + ' settings dialog'); + return _this; + } + + /** + * This gets called when an `CaptionSettingsMenuItem` is "clicked". See + * {@link ClickableComponent} for more detailed information on what a click can be. + * + * @param {EventTarget~Event} [event] + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click + */ + + + CaptionSettingsMenuItem.prototype.handleClick = function handleClick(event) { + this.player().getChild('textTrackSettings').open(); + }; + + return CaptionSettingsMenuItem; +}(_textTrackMenuItem2['default']); + +_component2['default'].registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem); +exports['default'] = CaptionSettingsMenuItem; + +},{"33":33,"5":5}],24:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _textTrackButton = _dereq_(32); + +var _textTrackButton2 = _interopRequireDefault(_textTrackButton); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _captionSettingsMenuItem = _dereq_(23); + +var _captionSettingsMenuItem2 = _interopRequireDefault(_captionSettingsMenuItem); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file captions-button.js + */ + + +/** + * The button component for toggling and selecting captions + * + * @extends TextTrackButton + */ +var CaptionsButton = function (_TextTrackButton) { + _inherits(CaptionsButton, _TextTrackButton); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + * + * @param {Component~ReadyCallback} [ready] + * The function to call when this component is ready. + */ + function CaptionsButton(player, options, ready) { + _classCallCheck(this, CaptionsButton); + + return _possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready)); + } + + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ + + + CaptionsButton.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-captions-button ' + _TextTrackButton.prototype.buildCSSClass.call(this); + }; + + CaptionsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { + return 'vjs-captions-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); + }; + + /** + * Create caption menu items + * + * @return {CaptionSettingsMenuItem[]} + * The array of current menu items. + */ + + + CaptionsButton.prototype.createItems = function createItems() { + var items = []; + + if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks)) { + items.push(new _captionSettingsMenuItem2['default'](this.player_, { kind: this.kind_ })); + + this.hideThreshold_ += 1; + } + + return _TextTrackButton.prototype.createItems.call(this, items); + }; + + return CaptionsButton; +}(_textTrackButton2['default']); + +/** + * `kind` of TextTrack to look for to associate it with this menu. + * + * @type {string} + * @private + */ + + +CaptionsButton.prototype.kind_ = 'captions'; + +/** + * The text that should display over the `CaptionsButton`s controls. Added for localization. + * + * @type {string} + * @private + */ +CaptionsButton.prototype.controlText_ = 'Captions'; + +_component2['default'].registerComponent('CaptionsButton', CaptionsButton); +exports['default'] = CaptionsButton; + +},{"23":23,"32":32,"5":5}],25:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _textTrackButton = _dereq_(32); + +var _textTrackButton2 = _interopRequireDefault(_textTrackButton); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _chaptersTrackMenuItem = _dereq_(26); + +var _chaptersTrackMenuItem2 = _interopRequireDefault(_chaptersTrackMenuItem); + +var _toTitleCase = _dereq_(96); + +var _toTitleCase2 = _interopRequireDefault(_toTitleCase); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file chapters-button.js + */ + + +/** + * The button component for toggling and selecting chapters + * Chapters act much differently than other text tracks + * Cues are navigation vs. other tracks of alternative languages + * + * @extends TextTrackButton + */ +var ChaptersButton = function (_TextTrackButton) { + _inherits(ChaptersButton, _TextTrackButton); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + * + * @param {Component~ReadyCallback} [ready] + * The function to call when this function is ready. + */ + function ChaptersButton(player, options, ready) { + _classCallCheck(this, ChaptersButton); + + return _possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready)); + } + + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ + + + ChaptersButton.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-chapters-button ' + _TextTrackButton.prototype.buildCSSClass.call(this); + }; + + ChaptersButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { + return 'vjs-chapters-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); + }; + + /** + * Update the menu based on the current state of its items. + * + * @param {EventTarget~Event} [event] + * An event that triggered this function to run. + * + * @listens TextTrackList#addtrack + * @listens TextTrackList#removetrack + * @listens TextTrackList#change + */ + + + ChaptersButton.prototype.update = function update(event) { + if (!this.track_ || event && (event.type === 'addtrack' || event.type === 'removetrack')) { + this.setTrack(this.findChaptersTrack()); + } + _TextTrackButton.prototype.update.call(this); + }; + + /** + * Set the currently selected track for the chapters button. + * + * @param {TextTrack} track + * The new track to select. Nothing will change if this is the currently selected + * track. + */ + + + ChaptersButton.prototype.setTrack = function setTrack(track) { + if (this.track_ === track) { + return; + } + + if (!this.updateHandler_) { + this.updateHandler_ = this.update.bind(this); + } + + // here this.track_ refers to the old track instance + if (this.track_) { + var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); + + if (remoteTextTrackEl) { + remoteTextTrackEl.removeEventListener('load', this.updateHandler_); + } + + this.track_ = null; + } + + this.track_ = track; + + // here this.track_ refers to the new track instance + if (this.track_) { + this.track_.mode = 'hidden'; + + var _remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); + + if (_remoteTextTrackEl) { + _remoteTextTrackEl.addEventListener('load', this.updateHandler_); + } + } + }; + + /** + * Find the track object that is currently in use by this ChaptersButton + * + * @return {TextTrack|undefined} + * The current track or undefined if none was found. + */ + + + ChaptersButton.prototype.findChaptersTrack = function findChaptersTrack() { + var tracks = this.player_.textTracks() || []; + + for (var i = tracks.length - 1; i >= 0; i--) { + // We will always choose the last track as our chaptersTrack + var track = tracks[i]; + + if (track.kind === this.kind_) { + return track; + } + } + }; + + /** + * Get the caption for the ChaptersButton based on the track label. This will also + * use the current tracks localized kind as a fallback if a label does not exist. + * + * @return {string} + * The tracks current label or the localized track kind. + */ + + + ChaptersButton.prototype.getMenuCaption = function getMenuCaption() { + if (this.track_ && this.track_.label) { + return this.track_.label; + } + return this.localize((0, _toTitleCase2['default'])(this.kind_)); + }; + + /** + * Create menu from chapter track + * + * @return {Menu} + * New menu for the chapter buttons + */ + + + ChaptersButton.prototype.createMenu = function createMenu() { + this.options_.title = this.getMenuCaption(); + return _TextTrackButton.prototype.createMenu.call(this); + }; + + /** + * Create a menu item for each text track + * + * @return {TextTrackMenuItem[]} + * Array of menu items + */ + + + ChaptersButton.prototype.createItems = function createItems() { + var items = []; + + if (!this.track_) { + return items; + } + + var cues = this.track_.cues; + + if (!cues) { + return items; + } + + for (var i = 0, l = cues.length; i < l; i++) { + var cue = cues[i]; + var mi = new _chaptersTrackMenuItem2['default'](this.player_, { track: this.track_, cue: cue }); + + items.push(mi); + } + + return items; + }; + + return ChaptersButton; +}(_textTrackButton2['default']); + +/** + * `kind` of TextTrack to look for to associate it with this menu. + * + * @type {string} + * @private + */ + + +ChaptersButton.prototype.kind_ = 'chapters'; + +/** + * The text that should display over the `ChaptersButton`s controls. Added for localization. + * + * @type {string} + * @private + */ +ChaptersButton.prototype.controlText_ = 'Chapters'; + +_component2['default'].registerComponent('ChaptersButton', ChaptersButton); +exports['default'] = ChaptersButton; + +},{"26":26,"32":32,"5":5,"96":96}],26:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _menuItem = _dereq_(51); + +var _menuItem2 = _interopRequireDefault(_menuItem); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _fn = _dereq_(88); + +var Fn = _interopRequireWildcard(_fn); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file chapters-track-menu-item.js + */ + + +/** + * The chapter track menu item + * + * @extends MenuItem + */ +var ChaptersTrackMenuItem = function (_MenuItem) { + _inherits(ChaptersTrackMenuItem, _MenuItem); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function ChaptersTrackMenuItem(player, options) { + _classCallCheck(this, ChaptersTrackMenuItem); + + var track = options.track; + var cue = options.cue; + var currentTime = player.currentTime(); + + // Modify options for parent MenuItem class's init. + options.selectable = true; + options.label = cue.text; + options.selected = cue.startTime <= currentTime && currentTime < cue.endTime; + + var _this = _possibleConstructorReturn(this, _MenuItem.call(this, player, options)); + + _this.track = track; + _this.cue = cue; + track.addEventListener('cuechange', Fn.bind(_this, _this.update)); + return _this; + } + + /** + * This gets called when an `ChaptersTrackMenuItem` is "clicked". See + * {@link ClickableComponent} for more detailed information on what a click can be. + * + * @param {EventTarget~Event} [event] + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click + */ + + + ChaptersTrackMenuItem.prototype.handleClick = function handleClick(event) { + _MenuItem.prototype.handleClick.call(this); + this.player_.currentTime(this.cue.startTime); + this.update(this.cue.startTime); + }; + + /** + * Update chapter menu item + * + * @param {EventTarget~Event} [event] + * The `cuechange` event that caused this function to run. + * + * @listens TextTrack#cuechange + */ + + + ChaptersTrackMenuItem.prototype.update = function update(event) { + var cue = this.cue; + var currentTime = this.player_.currentTime(); + + // vjs.log(currentTime, cue.startTime); + this.selected(cue.startTime <= currentTime && currentTime < cue.endTime); + }; + + return ChaptersTrackMenuItem; +}(_menuItem2['default']); + +_component2['default'].registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem); +exports['default'] = ChaptersTrackMenuItem; + +},{"5":5,"51":51,"88":88}],27:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _textTrackButton = _dereq_(32); + +var _textTrackButton2 = _interopRequireDefault(_textTrackButton); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _fn = _dereq_(88); + +var Fn = _interopRequireWildcard(_fn); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file descriptions-button.js + */ + + +/** + * The button component for toggling and selecting descriptions + * + * @extends TextTrackButton + */ +var DescriptionsButton = function (_TextTrackButton) { + _inherits(DescriptionsButton, _TextTrackButton); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + * + * @param {Component~ReadyCallback} [ready] + * The function to call when this component is ready. + */ + function DescriptionsButton(player, options, ready) { + _classCallCheck(this, DescriptionsButton); + + var _this = _possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready)); + + var tracks = player.textTracks(); + var changeHandler = Fn.bind(_this, _this.handleTracksChange); + + tracks.addEventListener('change', changeHandler); + _this.on('dispose', function () { + tracks.removeEventListener('change', changeHandler); + }); + return _this; + } + + /** + * Handle text track change + * + * @param {EventTarget~Event} event + * The event that caused this function to run + * + * @listens TextTrackList#change + */ + + + DescriptionsButton.prototype.handleTracksChange = function handleTracksChange(event) { + var tracks = this.player().textTracks(); + var disabled = false; + + // Check whether a track of a different kind is showing + for (var i = 0, l = tracks.length; i < l; i++) { + var track = tracks[i]; + + if (track.kind !== this.kind_ && track.mode === 'showing') { + disabled = true; + break; + } + } + + // If another track is showing, disable this menu button + if (disabled) { + this.disable(); + } else { + this.enable(); + } + }; + + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ + + + DescriptionsButton.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-descriptions-button ' + _TextTrackButton.prototype.buildCSSClass.call(this); + }; + + DescriptionsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { + return 'vjs-descriptions-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); + }; + + return DescriptionsButton; +}(_textTrackButton2['default']); + +/** + * `kind` of TextTrack to look for to associate it with this menu. + * + * @type {string} + * @private + */ + + +DescriptionsButton.prototype.kind_ = 'descriptions'; + +/** + * The text that should display over the `DescriptionsButton`s controls. Added for localization. + * + * @type {string} + * @private + */ +DescriptionsButton.prototype.controlText_ = 'Descriptions'; + +_component2['default'].registerComponent('DescriptionsButton', DescriptionsButton); +exports['default'] = DescriptionsButton; + +},{"32":32,"5":5,"88":88}],28:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _textTrackMenuItem = _dereq_(33); + +var _textTrackMenuItem2 = _interopRequireDefault(_textTrackMenuItem); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file off-text-track-menu-item.js + */ + + +/** + * A special menu item for turning of a specific type of text track + * + * @extends TextTrackMenuItem + */ +var OffTextTrackMenuItem = function (_TextTrackMenuItem) { + _inherits(OffTextTrackMenuItem, _TextTrackMenuItem); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function OffTextTrackMenuItem(player, options) { + _classCallCheck(this, OffTextTrackMenuItem); + + // Create pseudo track info + // Requires options['kind'] + options.track = { + player: player, + kind: options.kind, + kinds: options.kinds, + 'default': false, + mode: 'disabled' + }; + + if (!options.kinds) { + options.kinds = [options.kind]; + } + + if (options.label) { + options.track.label = options.label; + } else { + options.track.label = options.kinds.join(' and ') + ' off'; + } + + // MenuItem is selectable + options.selectable = true; + + var _this = _possibleConstructorReturn(this, _TextTrackMenuItem.call(this, player, options)); + + _this.selected(true); + return _this; + } + + /** + * Handle text track change + * + * @param {EventTarget~Event} event + * The event that caused this function to run + */ + + + OffTextTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) { + var tracks = this.player().textTracks(); + var selected = true; + + for (var i = 0, l = tracks.length; i < l; i++) { + var track = tracks[i]; + + if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') { + selected = false; + break; + } + } + + this.selected(selected); + }; + + return OffTextTrackMenuItem; +}(_textTrackMenuItem2['default']); + +_component2['default'].registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem); +exports['default'] = OffTextTrackMenuItem; + +},{"33":33,"5":5}],29:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _textTrackButton = _dereq_(32); + +var _textTrackButton2 = _interopRequireDefault(_textTrackButton); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _captionSettingsMenuItem = _dereq_(23); + +var _captionSettingsMenuItem2 = _interopRequireDefault(_captionSettingsMenuItem); + +var _subsCapsMenuItem = _dereq_(30); + +var _subsCapsMenuItem2 = _interopRequireDefault(_subsCapsMenuItem); + +var _toTitleCase = _dereq_(96); + +var _toTitleCase2 = _interopRequireDefault(_toTitleCase); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file sub-caps-button.js + */ + + +/** + * The button component for toggling and selecting captions and/or subtitles + * + * @extends TextTrackButton + */ +var SubsCapsButton = function (_TextTrackButton) { + _inherits(SubsCapsButton, _TextTrackButton); + + function SubsCapsButton(player) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + _classCallCheck(this, SubsCapsButton); + + // Although North America uses "captions" in most cases for + // "captions and subtitles" other locales use "subtitles" + var _this = _possibleConstructorReturn(this, _TextTrackButton.call(this, player, options)); + + _this.label_ = 'subtitles'; + if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(_this.player_.language_) > -1) { + _this.label_ = 'captions'; + } + _this.menuButton_.controlText((0, _toTitleCase2['default'])(_this.label_)); + return _this; + } + + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ + + + SubsCapsButton.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-subs-caps-button ' + _TextTrackButton.prototype.buildCSSClass.call(this); + }; + + SubsCapsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { + return 'vjs-subs-caps-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); + }; + + /** + * Create caption/subtitles menu items + * + * @return {CaptionSettingsMenuItem[]} + * The array of current menu items. + */ + + + SubsCapsButton.prototype.createItems = function createItems() { + var items = []; + + if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks)) { + items.push(new _captionSettingsMenuItem2['default'](this.player_, { kind: this.label_ })); + + this.hideThreshold_ += 1; + } + + items = _TextTrackButton.prototype.createItems.call(this, items, _subsCapsMenuItem2['default']); + return items; + }; + + return SubsCapsButton; +}(_textTrackButton2['default']); + +/** + * `kind`s of TextTrack to look for to associate it with this menu. + * + * @type {array} + * @private + */ + + +SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles']; + +/** + * The text that should display over the `SubsCapsButton`s controls. + * + * + * @type {string} + * @private + */ +SubsCapsButton.prototype.controlText_ = 'Subtitles'; + +_component2['default'].registerComponent('SubsCapsButton', SubsCapsButton); +exports['default'] = SubsCapsButton; + +},{"23":23,"30":30,"32":32,"5":5,"96":96}],30:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _textTrackMenuItem = _dereq_(33); + +var _textTrackMenuItem2 = _interopRequireDefault(_textTrackMenuItem); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _obj = _dereq_(93); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file subs-caps-menu-item.js + */ + + +/** + * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles + * in the SubsCapsMenu. + * + * @extends TextTrackMenuItem + */ +var SubsCapsMenuItem = function (_TextTrackMenuItem) { + _inherits(SubsCapsMenuItem, _TextTrackMenuItem); + + function SubsCapsMenuItem() { + _classCallCheck(this, SubsCapsMenuItem); + + return _possibleConstructorReturn(this, _TextTrackMenuItem.apply(this, arguments)); + } + + SubsCapsMenuItem.prototype.createEl = function createEl(type, props, attrs) { + var innerHTML = '' + this.localize(this.options_.label); + + if (this.options_.track.kind === 'captions') { + innerHTML += '\n \n ' + this.localize('Captions') + '\n '; + } + + innerHTML += ''; + + var el = _TextTrackMenuItem.prototype.createEl.call(this, type, (0, _obj.assign)({ + innerHTML: innerHTML + }, props), attrs); + + return el; + }; + + return SubsCapsMenuItem; +}(_textTrackMenuItem2['default']); + +_component2['default'].registerComponent('SubsCapsMenuItem', SubsCapsMenuItem); +exports['default'] = SubsCapsMenuItem; + +},{"33":33,"5":5,"93":93}],31:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _textTrackButton = _dereq_(32); + +var _textTrackButton2 = _interopRequireDefault(_textTrackButton); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file subtitles-button.js + */ + + +/** + * The button component for toggling and selecting subtitles + * + * @extends TextTrackButton + */ +var SubtitlesButton = function (_TextTrackButton) { + _inherits(SubtitlesButton, _TextTrackButton); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + * + * @param {Component~ReadyCallback} [ready] + * The function to call when this component is ready. + */ + function SubtitlesButton(player, options, ready) { + _classCallCheck(this, SubtitlesButton); + + return _possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready)); + } + + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ + + + SubtitlesButton.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-subtitles-button ' + _TextTrackButton.prototype.buildCSSClass.call(this); + }; + + SubtitlesButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { + return 'vjs-subtitles-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); + }; + + return SubtitlesButton; +}(_textTrackButton2['default']); + +/** + * `kind` of TextTrack to look for to associate it with this menu. + * + * @type {string} + * @private + */ + + +SubtitlesButton.prototype.kind_ = 'subtitles'; + +/** + * The text that should display over the `SubtitlesButton`s controls. Added for localization. + * + * @type {string} + * @private + */ +SubtitlesButton.prototype.controlText_ = 'Subtitles'; + +_component2['default'].registerComponent('SubtitlesButton', SubtitlesButton); +exports['default'] = SubtitlesButton; + +},{"32":32,"5":5}],32:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _trackButton = _dereq_(38); + +var _trackButton2 = _interopRequireDefault(_trackButton); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _textTrackMenuItem = _dereq_(33); + +var _textTrackMenuItem2 = _interopRequireDefault(_textTrackMenuItem); + +var _offTextTrackMenuItem = _dereq_(28); + +var _offTextTrackMenuItem2 = _interopRequireDefault(_offTextTrackMenuItem); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file text-track-button.js + */ + + +/** + * The base class for buttons that toggle specific text track types (e.g. subtitles) + * + * @extends MenuButton + */ +var TextTrackButton = function (_TrackButton) { + _inherits(TextTrackButton, _TrackButton); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options={}] + * The key/value store of player options. + */ + function TextTrackButton(player) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + _classCallCheck(this, TextTrackButton); + + options.tracks = player.textTracks(); + + return _possibleConstructorReturn(this, _TrackButton.call(this, player, options)); + } + + /** + * Create a menu item for each text track + * + * @param {TextTrackMenuItem[]} [items=[]] + * Existing array of items to use during creation + * + * @return {TextTrackMenuItem[]} + * Array of menu items that were created + */ + + + TextTrackButton.prototype.createItems = function createItems() { + var items = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + var TrackMenuItem = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _textTrackMenuItem2['default']; + + + // Label is an overide for the [track] off label + // USed to localise captions/subtitles + var label = void 0; + + if (this.label_) { + label = this.label_ + ' off'; + } + // Add an OFF menu item to turn all tracks off + items.push(new _offTextTrackMenuItem2['default'](this.player_, { + kinds: this.kinds_, + kind: this.kind_, + label: label + })); + + this.hideThreshold_ += 1; + + var tracks = this.player_.textTracks(); + + if (!Array.isArray(this.kinds_)) { + this.kinds_ = [this.kind_]; + } + + for (var i = 0; i < tracks.length; i++) { + var track = tracks[i]; + + // only add tracks that are of an appropriate kind and have a label + if (this.kinds_.indexOf(track.kind) > -1) { + var item = new TrackMenuItem(this.player_, { + track: track, + // MenuItem is selectable + selectable: true + }); + + item.addClass('vjs-' + track.kind + '-menu-item'); + items.push(item); + } + } + + return items; + }; + + return TextTrackButton; +}(_trackButton2['default']); + +_component2['default'].registerComponent('TextTrackButton', TextTrackButton); +exports['default'] = TextTrackButton; + +},{"28":28,"33":33,"38":38,"5":5}],33:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + +var _menuItem = _dereq_(51); + +var _menuItem2 = _interopRequireDefault(_menuItem); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _fn = _dereq_(88); + +var Fn = _interopRequireWildcard(_fn); + +var _window = _dereq_(100); + +var _window2 = _interopRequireDefault(_window); + +var _document = _dereq_(99); + +var _document2 = _interopRequireDefault(_document); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file text-track-menu-item.js + */ + + +/** + * The specific menu item type for selecting a language within a text track kind + * + * @extends MenuItem + */ +var TextTrackMenuItem = function (_MenuItem) { + _inherits(TextTrackMenuItem, _MenuItem); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function TextTrackMenuItem(player, options) { + _classCallCheck(this, TextTrackMenuItem); + + var track = options.track; + var tracks = player.textTracks(); + + // Modify options for parent MenuItem class's init. + options.label = track.label || track.language || 'Unknown'; + options.selected = track['default'] || track.mode === 'showing'; + + var _this = _possibleConstructorReturn(this, _MenuItem.call(this, player, options)); + + _this.track = track; + var changeHandler = Fn.bind(_this, _this.handleTracksChange); + + player.on(['loadstart', 'texttrackchange'], changeHandler); + tracks.addEventListener('change', changeHandler); + _this.on('dispose', function () { + tracks.removeEventListener('change', changeHandler); + }); + + // iOS7 doesn't dispatch change events to TextTrackLists when an + // associated track's mode changes. Without something like + // Object.observe() (also not present on iOS7), it's not + // possible to detect changes to the mode attribute and polyfill + // the change event. As a poor substitute, we manually dispatch + // change events whenever the controls modify the mode. + if (tracks.onchange === undefined) { + var event = void 0; + + _this.on(['tap', 'click'], function () { + if (_typeof(_window2['default'].Event) !== 'object') { + // Android 2.3 throws an Illegal Constructor error for window.Event + try { + event = new _window2['default'].Event('change'); + } catch (err) { + // continue regardless of error + } + } + + if (!event) { + event = _document2['default'].createEvent('Event'); + event.initEvent('change', true, true); + } + + tracks.dispatchEvent(event); + }); + } + return _this; + } + + /** + * This gets called when an `TextTrackMenuItem` is "clicked". See + * {@link ClickableComponent} for more detailed information on what a click can be. + * + * @param {EventTarget~Event} event + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click + */ + + + TextTrackMenuItem.prototype.handleClick = function handleClick(event) { + var kind = this.track.kind; + var kinds = this.track.kinds; + var tracks = this.player_.textTracks(); + + if (!kinds) { + kinds = [kind]; + } + + _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 && kinds.indexOf(track.kind) > -1) { + if (track.mode !== 'showing') { + track.mode = 'showing'; + } + } else if (track.mode !== 'disabled') { + track.mode = 'disabled'; + } + } + }; + + /** + * Handle text track list change + * + * @param {EventTarget~Event} event + * The `change` event that caused this function to be called. + * + * @listens TextTrackList#change + */ + + + TextTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) { + this.selected(this.track.mode === 'showing'); + }; + + return TextTrackMenuItem; +}(_menuItem2['default']); + +_component2['default'].registerComponent('TextTrackMenuItem', TextTrackMenuItem); +exports['default'] = TextTrackMenuItem; + +},{"100":100,"5":5,"51":51,"88":88,"99":99}],34:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _dom = _dereq_(85); + +var Dom = _interopRequireWildcard(_dom); + +var _formatTime = _dereq_(89); + +var _formatTime2 = _interopRequireDefault(_formatTime); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file current-time-display.js + */ + + +/** + * Displays the current time + * + * @extends Component + */ +var CurrentTimeDisplay = function (_Component) { + _inherits(CurrentTimeDisplay, _Component); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function CurrentTimeDisplay(player, options) { + _classCallCheck(this, CurrentTimeDisplay); + + var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); + + _this.on(player, 'timeupdate', _this.updateContent); + return _this; + } + + /** + * Create the `Component`'s DOM element + * + * @return {Element} + * The element that was created. + */ + + + CurrentTimeDisplay.prototype.createEl = function createEl() { + var el = _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-current-time vjs-time-control vjs-control' + }); + + this.contentEl_ = Dom.createEl('div', { + className: 'vjs-current-time-display', + // label the current time for screen reader users + innerHTML: 'Current Time ' + '0:00' + }, { + // tell screen readers not to automatically read the time as it changes + 'aria-live': 'off' + }); + + el.appendChild(this.contentEl_); + return el; + }; + + /** + * Update current time display + * + * @param {EventTarget~Event} [event] + * The `timeupdate` event that caused this function to run. + * + * @listens Player#timeupdate + */ + + + CurrentTimeDisplay.prototype.updateContent = function updateContent(event) { + // Allows for smooth scrubbing, when player can't keep up. + var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); + var localizedText = this.localize('Current Time'); + var formattedTime = (0, _formatTime2['default'])(time, this.player_.duration()); + + if (formattedTime !== this.formattedTime_) { + this.formattedTime_ = formattedTime; + this.contentEl_.innerHTML = '' + localizedText + ' ' + formattedTime; + } + }; + + return CurrentTimeDisplay; +}(_component2['default']); + +_component2['default'].registerComponent('CurrentTimeDisplay', CurrentTimeDisplay); +exports['default'] = CurrentTimeDisplay; + +},{"5":5,"85":85,"89":89}],35:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _dom = _dereq_(85); + +var Dom = _interopRequireWildcard(_dom); + +var _formatTime = _dereq_(89); + +var _formatTime2 = _interopRequireDefault(_formatTime); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file duration-display.js + */ + + +/** + * Displays the duration + * + * @extends Component + */ +var DurationDisplay = function (_Component) { + _inherits(DurationDisplay, _Component); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function DurationDisplay(player, options) { + _classCallCheck(this, DurationDisplay); + + var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); + + _this.on(player, 'durationchange', _this.updateContent); + + // Also listen for timeupdate and loadedmetadata because removing those + // listeners could have broken dependent applications/libraries. These + // can likely be removed for 6.0. + _this.on(player, 'timeupdate', _this.updateContent); + _this.on(player, 'loadedmetadata', _this.updateContent); + return _this; + } + + /** + * Create the `Component`'s DOM element + * + * @return {Element} + * The element that was created. + */ + + + DurationDisplay.prototype.createEl = function createEl() { + var el = _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-duration vjs-time-control vjs-control' + }); + + this.contentEl_ = Dom.createEl('div', { + className: 'vjs-duration-display', + // label the duration time for screen reader users + innerHTML: '' + this.localize('Duration Time') + ' 0:00' + }, { + // tell screen readers not to automatically read the time as it changes + 'aria-live': 'off' + }); + + el.appendChild(this.contentEl_); + return el; + }; + + /** + * Update duration time display. + * + * @param {EventTarget~Event} [event] + * The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused + * this function to be called. + * + * @listens Player#durationchange + * @listens Player#timeupdate + * @listens Player#loadedmetadata + */ + + + DurationDisplay.prototype.updateContent = function updateContent(event) { + var duration = this.player_.duration(); + + if (duration && this.duration_ !== duration) { + this.duration_ = duration; + var localizedText = this.localize('Duration Time'); + var formattedTime = (0, _formatTime2['default'])(duration); + + // label the duration time for screen reader users + this.contentEl_.innerHTML = '' + localizedText + ' ' + formattedTime; + } + }; + + return DurationDisplay; +}(_component2['default']); + +_component2['default'].registerComponent('DurationDisplay', DurationDisplay); +exports['default'] = DurationDisplay; + +},{"5":5,"85":85,"89":89}],36:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _dom = _dereq_(85); + +var Dom = _interopRequireWildcard(_dom); + +var _formatTime = _dereq_(89); + +var _formatTime2 = _interopRequireDefault(_formatTime); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file remaining-time-display.js + */ + + +/** + * Displays the time left in the video + * + * @extends Component + */ +var RemainingTimeDisplay = function (_Component) { + _inherits(RemainingTimeDisplay, _Component); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function RemainingTimeDisplay(player, options) { + _classCallCheck(this, RemainingTimeDisplay); + + var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); + + _this.on(player, 'timeupdate', _this.updateContent); + _this.on(player, 'durationchange', _this.updateContent); + return _this; + } + + /** + * Create the `Component`'s DOM element + * + * @return {Element} + * The element that was created. + */ + + + RemainingTimeDisplay.prototype.createEl = function createEl() { + var el = _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-remaining-time vjs-time-control vjs-control' + }); + + this.contentEl_ = Dom.createEl('div', { + className: 'vjs-remaining-time-display', + // label the remaining time for screen reader users + innerHTML: '' + this.localize('Remaining Time') + ' -0:00' + }, { + // tell screen readers not to automatically read the time as it changes + 'aria-live': 'off' + }); + + el.appendChild(this.contentEl_); + return el; + }; + + /** + * Update remaining time display. + * + * @param {EventTarget~Event} [event] + * The `timeupdate` or `durationchange` event that caused this to run. + * + * @listens Player#timeupdate + * @listens Player#durationchange + */ + + + RemainingTimeDisplay.prototype.updateContent = function updateContent(event) { + if (this.player_.duration()) { + var localizedText = this.localize('Remaining Time'); + var formattedTime = (0, _formatTime2['default'])(this.player_.remainingTime()); + + if (formattedTime !== this.formattedTime_) { + this.formattedTime_ = formattedTime; + this.contentEl_.innerHTML = '' + localizedText + ' -' + formattedTime; + } + } + + // 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()); + }; + + return RemainingTimeDisplay; +}(_component2['default']); + +_component2['default'].registerComponent('RemainingTimeDisplay', RemainingTimeDisplay); +exports['default'] = RemainingTimeDisplay; + +},{"5":5,"85":85,"89":89}],37:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file time-divider.js + */ + + +/** + * The separator between the current time and duration. + * Can be hidden if it's not needed in the design. + * + * @extends Component + */ +var TimeDivider = function (_Component) { + _inherits(TimeDivider, _Component); + + function TimeDivider() { + _classCallCheck(this, TimeDivider); + + return _possibleConstructorReturn(this, _Component.apply(this, arguments)); + } + + /** + * Create the component's DOM element + * + * @return {Element} + * The element that was created. + */ + TimeDivider.prototype.createEl = function createEl() { + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-time-control vjs-time-divider', + innerHTML: '
/
' + }); + }; + + return TimeDivider; +}(_component2['default']); + +_component2['default'].registerComponent('TimeDivider', TimeDivider); +exports['default'] = TimeDivider; + +},{"5":5}],38:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _menuButton = _dereq_(50); + +var _menuButton2 = _interopRequireDefault(_menuButton); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _fn = _dereq_(88); + +var Fn = _interopRequireWildcard(_fn); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file track-button.js + */ + + +/** + * The base class for buttons that toggle specific track types (e.g. subtitles). + * + * @extends MenuButton + */ +var TrackButton = function (_MenuButton) { + _inherits(TrackButton, _MenuButton); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function TrackButton(player, options) { + _classCallCheck(this, TrackButton); + + var tracks = options.tracks; + + var _this = _possibleConstructorReturn(this, _MenuButton.call(this, player, options)); + + if (_this.items.length <= 1) { + _this.hide(); + } + + if (!tracks) { + return _possibleConstructorReturn(_this); + } + + var updateHandler = Fn.bind(_this, _this.update); + + tracks.addEventListener('removetrack', updateHandler); + tracks.addEventListener('addtrack', updateHandler); + _this.player_.on('ready', updateHandler); + + _this.player_.on('dispose', function () { + tracks.removeEventListener('removetrack', updateHandler); + tracks.removeEventListener('addtrack', updateHandler); + }); + return _this; + } + + return TrackButton; +}(_menuButton2['default']); + +_component2['default'].registerComponent('TrackButton', TrackButton); +exports['default'] = TrackButton; + +},{"5":5,"50":50,"88":88}],39:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; +/** + * Check if volume control is supported and if it isn't hide the + * `Component` that was passed using the `vjs-hidden` class. + * + * @param {Component} self + * The component that should be hidden if volume is unsupported + * + * @param {Player} player + * A reference to the player + * + * @private + */ +var checkVolumeSupport = function checkVolumeSupport(self, player) { + // hide volume controls when they're not supported by the current tech + if (player.tech_ && !player.tech_.featuresVolumeControl) { + self.addClass('vjs-hidden'); + } + + self.on(player, 'loadstart', function () { + if (!player.tech_.featuresVolumeControl) { + self.addClass('vjs-hidden'); + } else { + self.removeClass('vjs-hidden'); + } + }); +}; + +exports['default'] = checkVolumeSupport; + +},{}],40:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _slider = _dereq_(60); + +var _slider2 = _interopRequireDefault(_slider); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +_dereq_(42); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file volume-bar.js + */ + + +// Required children + + +/** + * The bar that contains the volume level and can be clicked on to adjust the level + * + * @extends Slider + */ +var VolumeBar = function (_Slider) { + _inherits(VolumeBar, _Slider); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function VolumeBar(player, options) { + _classCallCheck(this, VolumeBar); + + var _this = _possibleConstructorReturn(this, _Slider.call(this, player, options)); + + _this.on('slideractive', _this.updateLastVolume_); + _this.on(player, 'volumechange', _this.updateARIAAttributes); + player.ready(function () { + return _this.updateARIAAttributes(); + }); + return _this; + } + + /** + * Create the `Component`'s DOM element + * + * @return {Element} + * The element that was created. + */ + + + VolumeBar.prototype.createEl = function createEl() { + return _Slider.prototype.createEl.call(this, 'div', { + className: 'vjs-volume-bar vjs-slider-bar' + }, { + 'aria-label': this.localize('Volume Level'), + 'aria-live': 'polite' + }); + }; + + /** + * Handle movement events on the {@link VolumeMenuButton}. + * + * @param {EventTarget~Event} event + * The event that caused this function to run. + * + * @listens mousemove + */ + + + VolumeBar.prototype.handleMouseMove = function handleMouseMove(event) { + this.checkMuted(); + this.player_.volume(this.calculateDistance(event)); + }; + + /** + * If the player is muted unmute it. + */ + + + VolumeBar.prototype.checkMuted = function checkMuted() { + if (this.player_.muted()) { + this.player_.muted(false); + } + }; + + /** + * Get percent of volume level + * + * @return {number} + * Volume level percent as a decimal number. + */ + + + VolumeBar.prototype.getPercent = function getPercent() { + if (this.player_.muted()) { + return 0; + } + return this.player_.volume(); + }; + + /** + * Increase volume level for keyboard users + */ + + + VolumeBar.prototype.stepForward = function stepForward() { + this.checkMuted(); + this.player_.volume(this.player_.volume() + 0.1); + }; + + /** + * Decrease volume level for keyboard users + */ + + + VolumeBar.prototype.stepBack = function stepBack() { + this.checkMuted(); + this.player_.volume(this.player_.volume() - 0.1); + }; + + /** + * Update ARIA accessibility attributes + * + * @param {EventTarget~Event} [event] + * The `volumechange` event that caused this function to run. + * + * @listens Player#volumechange + */ + + + VolumeBar.prototype.updateARIAAttributes = function updateARIAAttributes(event) { + var ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_(); + + this.el_.setAttribute('aria-valuenow', ariaValue); + this.el_.setAttribute('aria-valuetext', ariaValue + '%'); + }; + + /** + * Returns the current value of the player volume as a percentage + * + * @private + */ + + + VolumeBar.prototype.volumeAsPercentage_ = function volumeAsPercentage_() { + return Math.round(this.player_.volume() * 100); + }; + + /** + * When user starts dragging the VolumeBar, store the volume and listen for + * the end of the drag. When the drag ends, if the volume was set to zero, + * set lastVolume to the stored volume. + * + * @listens slideractive + * @private + */ + + + VolumeBar.prototype.updateLastVolume_ = function updateLastVolume_() { + var _this2 = this; + + var volumeBeforeDrag = this.player_.volume(); + + this.one('sliderinactive', function () { + if (_this2.player_.volume() === 0) { + _this2.player_.lastVolume_(volumeBeforeDrag); + } + }); + }; + + return VolumeBar; +}(_slider2['default']); + +/** + * Default options for the `VolumeBar` + * + * @type {Object} + * @private + */ + + +VolumeBar.prototype.options_ = { + children: ['volumeLevel'], + barName: 'volumeLevel' +}; + +/** + * Call the update event for this Slider when this event happens on the player. + * + * @type {string} + */ +VolumeBar.prototype.playerEvent = 'volumechange'; + +_component2['default'].registerComponent('VolumeBar', VolumeBar); +exports['default'] = VolumeBar; + +},{"42":42,"5":5,"60":60}],41:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _checkVolumeSupport = _dereq_(39); + +var _checkVolumeSupport2 = _interopRequireDefault(_checkVolumeSupport); + +var _obj = _dereq_(93); + +var _fn = _dereq_(88); + +_dereq_(40); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file volume-control.js + */ + + +// Required children + + +/** + * The component for controlling the volume level + * + * @extends Component + */ +var VolumeControl = function (_Component) { + _inherits(VolumeControl, _Component); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options={}] + * The key/value store of player options. + */ + function VolumeControl(player) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + _classCallCheck(this, VolumeControl); + + options.vertical = options.vertical || false; + + // Pass the vertical option down to the VolumeBar if + // the VolumeBar is turned on. + if (typeof options.volumeBar === 'undefined' || (0, _obj.isPlain)(options.volumeBar)) { + options.volumeBar = options.volumeBar || {}; + options.volumeBar.vertical = options.vertical; + } + + // hide this control if volume support is missing + var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); + + (0, _checkVolumeSupport2['default'])(_this, player); + + _this.throttledHandleMouseMove = (0, _fn.throttle)((0, _fn.bind)(_this, _this.handleMouseMove), 25); + + _this.on('mousedown', _this.handleMouseDown); + _this.on('touchstart', _this.handleMouseDown); + + // while the slider is active (the mouse has been pressed down and + // is dragging) or in focus we do not want to hide the VolumeBar + _this.on(_this.volumeBar, ['focus', 'slideractive'], function () { + _this.volumeBar.addClass('vjs-slider-active'); + _this.addClass('vjs-slider-active'); + _this.trigger('slideractive'); + }); + + _this.on(_this.volumeBar, ['blur', 'sliderinactive'], function () { + _this.volumeBar.removeClass('vjs-slider-active'); + _this.removeClass('vjs-slider-active'); + _this.trigger('sliderinactive'); + }); + return _this; + } + + /** + * Create the `Component`'s DOM element + * + * @return {Element} + * The element that was created. + */ + + + VolumeControl.prototype.createEl = function createEl() { + var orientationClass = 'vjs-volume-horizontal'; + + if (this.options_.vertical) { + orientationClass = 'vjs-volume-vertical'; + } + + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-volume-control vjs-control ' + orientationClass + }); + }; + + /** + * Handle `mousedown` or `touchstart` events on the `VolumeControl`. + * + * @param {EventTarget~Event} event + * `mousedown` or `touchstart` event that triggered this function + * + * @listens mousedown + * @listens touchstart + */ + + + VolumeControl.prototype.handleMouseDown = function handleMouseDown(event) { + var doc = this.el_.ownerDocument; + + this.on(doc, 'mousemove', this.throttledHandleMouseMove); + this.on(doc, 'touchmove', this.throttledHandleMouseMove); + this.on(doc, 'mouseup', this.handleMouseUp); + this.on(doc, 'touchend', this.handleMouseUp); + }; + + /** + * Handle `mouseup` or `touchend` events on the `VolumeControl`. + * + * @param {EventTarget~Event} event + * `mouseup` or `touchend` event that triggered this function. + * + * @listens touchend + * @listens mouseup + */ + + + VolumeControl.prototype.handleMouseUp = function handleMouseUp(event) { + var doc = this.el_.ownerDocument; + + this.off(doc, 'mousemove', this.throttledHandleMouseMove); + this.off(doc, 'touchmove', this.throttledHandleMouseMove); + this.off(doc, 'mouseup', this.handleMouseUp); + this.off(doc, 'touchend', this.handleMouseUp); + }; + + /** + * Handle `mousedown` or `touchstart` events on the `VolumeControl`. + * + * @param {EventTarget~Event} event + * `mousedown` or `touchstart` event that triggered this function + * + * @listens mousedown + * @listens touchstart + */ + + + VolumeControl.prototype.handleMouseMove = function handleMouseMove(event) { + this.volumeBar.handleMouseMove(event); + }; + + return VolumeControl; +}(_component2['default']); + +/** + * Default options for the `VolumeControl` + * + * @type {Object} + * @private + */ + + +VolumeControl.prototype.options_ = { + children: ['volumeBar'] +}; + +_component2['default'].registerComponent('VolumeControl', VolumeControl); +exports['default'] = VolumeControl; + +},{"39":39,"40":40,"5":5,"88":88,"93":93}],42:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file volume-level.js + */ + + +/** + * Shows volume level + * + * @extends Component + */ +var VolumeLevel = function (_Component) { + _inherits(VolumeLevel, _Component); + + function VolumeLevel() { + _classCallCheck(this, VolumeLevel); + + return _possibleConstructorReturn(this, _Component.apply(this, arguments)); + } + + /** + * Create the `Component`'s DOM element + * + * @return {Element} + * The element that was created. + */ + VolumeLevel.prototype.createEl = function createEl() { + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-volume-level', + innerHTML: '' + }); + }; + + return VolumeLevel; +}(_component2['default']); + +_component2['default'].registerComponent('VolumeLevel', VolumeLevel); +exports['default'] = VolumeLevel; + +},{"5":5}],43:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _checkVolumeSupport = _dereq_(39); + +var _checkVolumeSupport2 = _interopRequireDefault(_checkVolumeSupport); + +var _obj = _dereq_(93); + +_dereq_(41); + +_dereq_(11); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file volume-control.js + */ + + +// Required children + + +/** + * A Component to contain the MuteToggle and VolumeControl so that + * they can work together. + * + * @extends Component + */ +var VolumePanel = function (_Component) { + _inherits(VolumePanel, _Component); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options={}] + * The key/value store of player options. + */ + function VolumePanel(player) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + _classCallCheck(this, VolumePanel); + + if (typeof options.inline !== 'undefined') { + options.inline = options.inline; + } else { + options.inline = true; + } + + // pass the inline option down to the VolumeControl as vertical if + // the VolumeControl is on. + if (typeof options.volumeControl === 'undefined' || (0, _obj.isPlain)(options.volumeControl)) { + options.volumeControl = options.volumeControl || {}; + options.volumeControl.vertical = !options.inline; + } + + // hide this control if volume support is missing + var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); + + (0, _checkVolumeSupport2['default'])(_this, player); + + // while the slider is active (the mouse has been pressed down and + // is dragging) or in focus we do not want to hide the VolumeBar + _this.on(_this.volumeControl, ['slideractive'], _this.sliderActive_); + _this.on(_this.muteToggle, 'focus', _this.sliderActive_); + + _this.on(_this.volumeControl, ['sliderinactive'], _this.sliderInactive_); + _this.on(_this.muteToggle, 'blur', _this.sliderInactive_); + return _this; + } + + /** + * Add vjs-slider-active class to the VolumePanel + * + * @listens VolumeControl#slideractive + * @private + */ + + + VolumePanel.prototype.sliderActive_ = function sliderActive_() { + this.addClass('vjs-slider-active'); + }; + + /** + * Removes vjs-slider-active class to the VolumePanel + * + * @listens VolumeControl#sliderinactive + * @private + */ + + + VolumePanel.prototype.sliderInactive_ = function sliderInactive_() { + this.removeClass('vjs-slider-active'); + }; + + /** + * Create the `Component`'s DOM element + * + * @return {Element} + * The element that was created. + */ + + + VolumePanel.prototype.createEl = function createEl() { + var orientationClass = 'vjs-volume-panel-horizontal'; + + if (!this.options_.inline) { + orientationClass = 'vjs-volume-panel-vertical'; + } + + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-volume-panel vjs-control ' + orientationClass + }); + }; + + return VolumePanel; +}(_component2['default']); + +/** + * Default options for the `VolumeControl` + * + * @type {Object} + * @private + */ + + +VolumePanel.prototype.options_ = { + children: ['muteToggle', 'volumeControl'] +}; + +_component2['default'].registerComponent('VolumePanel', VolumePanel); +exports['default'] = VolumePanel; + +},{"11":11,"39":39,"41":41,"5":5,"93":93}],44:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _modalDialog = _dereq_(55); + +var _modalDialog2 = _interopRequireDefault(_modalDialog); + +var _mergeOptions = _dereq_(92); + +var _mergeOptions2 = _interopRequireDefault(_mergeOptions); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file error-display.js + */ + + +/** + * A display that indicates an error has occurred. This means that the video + * is unplayable. + * + * @extends ModalDialog + */ +var ErrorDisplay = function (_ModalDialog) { + _inherits(ErrorDisplay, _ModalDialog); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function ErrorDisplay(player, options) { + _classCallCheck(this, ErrorDisplay); + + var _this = _possibleConstructorReturn(this, _ModalDialog.call(this, player, options)); + + _this.on(player, 'error', _this.open); + return _this; + } + + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + * + * @deprecated Since version 5. + */ + + + ErrorDisplay.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-error-display ' + _ModalDialog.prototype.buildCSSClass.call(this); + }; + + /** + * Gets the localized error message based on the `Player`s error. + * + * @return {string} + * The `Player`s error message localized or an empty string. + */ + + + ErrorDisplay.prototype.content = function content() { + var error = this.player().error(); + + return error ? this.localize(error.message) : ''; + }; + + return ErrorDisplay; +}(_modalDialog2['default']); + +/** + * The default options for an `ErrorDisplay`. + * + * @private + */ + + +ErrorDisplay.prototype.options_ = (0, _mergeOptions2['default'])(_modalDialog2['default'].prototype.options_, { + pauseOnOpen: false, + fillAlways: true, + temporary: false, + uncloseable: true +}); + +_component2['default'].registerComponent('ErrorDisplay', ErrorDisplay); +exports['default'] = ErrorDisplay; + +},{"5":5,"55":55,"92":92}],45:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _events = _dereq_(86); + +var Events = _interopRequireWildcard(_events); + +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; } } + +/** + * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It + * adds shorthand functions that wrap around lengthy functions. For example: + * the `on` function is a wrapper around `addEventListener`. + * + * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget} + * @class EventTarget + */ +var EventTarget = function EventTarget() {}; + +/** + * A Custom DOM event. + * + * @typedef {Object} EventTarget~Event + * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent} + */ + +/** + * All event listeners should follow the following format. + * + * @callback EventTarget~EventListener + * @this {EventTarget} + * + * @param {EventTarget~Event} event + * the event that triggered this function + * + * @param {Object} [hash] + * hash of data sent during the event + */ + +/** + * An object containing event names as keys and booleans as values. + * + * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger} + * will have extra functionality. See that function for more information. + * + * @property EventTarget.prototype.allowedEvents_ + * @private + */ +/** + * @file src/js/event-target.js + */ +EventTarget.prototype.allowedEvents_ = {}; + +/** + * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a + * function that will get called when an event with a certain name gets triggered. + * + * @param {string|string[]} type + * An event name or an array of event names. + * + * @param {EventTarget~EventListener} fn + * The function to call with `EventTarget`s + */ +EventTarget.prototype.on = function (type, fn) { + // Remove the addEventListener alias before calling Events.on + // so we don't get into an infinite type loop + var ael = this.addEventListener; + + this.addEventListener = function () {}; + Events.on(this, type, fn); + this.addEventListener = ael; +}; + +/** + * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic + * the standard DOM API. + * + * @function + * @see {@link EventTarget#on} + */ +EventTarget.prototype.addEventListener = EventTarget.prototype.on; + +/** + * Removes an `event listener` for a specific event from an instance of `EventTarget`. + * This makes it so that the `event listener` will no longer get called when the + * named event happens. + * + * @param {string|string[]} type + * An event name or an array of event names. + * + * @param {EventTarget~EventListener} fn + * The function to remove. + */ +EventTarget.prototype.off = function (type, fn) { + Events.off(this, type, fn); +}; + +/** + * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic + * the standard DOM API. + * + * @function + * @see {@link EventTarget#off} + */ +EventTarget.prototype.removeEventListener = EventTarget.prototype.off; + +/** + * This function will add an `event listener` that gets triggered only once. After the + * first trigger it will get removed. This is like adding an `event listener` + * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself. + * + * @param {string|string[]} type + * An event name or an array of event names. + * + * @param {EventTarget~EventListener} fn + * The function to be called once for each event name. + */ +EventTarget.prototype.one = function (type, fn) { + // Remove the addEventListener alialing Events.on + // so we don't get into an infinite type loop + var ael = this.addEventListener; + + this.addEventListener = function () {}; + Events.one(this, type, fn); + this.addEventListener = ael; +}; + +/** + * This function causes an event to happen. This will then cause any `event listeners` + * that are waiting for that event, to get called. If there are no `event listeners` + * for an event then nothing will happen. + * + * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`. + * Trigger will also call the `on` + `uppercaseEventName` function. + * + * Example: + * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call + * `onClick` if it exists. + * + * @param {string|EventTarget~Event|Object} event + * The name of the event, an `Event`, or an object with a key of type set to + * an event name. + */ +EventTarget.prototype.trigger = function (event) { + var type = event.type || event; + + if (typeof event === 'string') { + event = { type: type }; + } + event = Events.fixEvent(event); + + if (this.allowedEvents_[type] && this['on' + type]) { + this['on' + type](event); + } + + Events.trigger(this, event); +}; + +/** + * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic + * the standard DOM API. + * + * @function + * @see {@link EventTarget#trigger} + */ +EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger; + +exports['default'] = EventTarget; + +},{"86":86}],46:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + +/** + * @file extend.js + * @module extend + */ + +/** + * A combination of node inherits and babel's inherits (after transpile). + * Both work the same but node adds `super_` to the subClass + * and Bable adds the superClass as __proto__. Both seem useful. + * + * @param {Object} subClass + * The class to inherit to + * + * @param {Object} superClass + * The class to inherit from + * + * @private + */ +var _inherits = 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 === 'undefined' ? 'undefined' : _typeof(superClass))); + } + + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true + } + }); + + if (superClass) { + // node + subClass.super_ = superClass; + } +}; + +/** + * Function for subclassing using the same inheritance that + * videojs uses internally + * + * @static + * @const + * + * @param {Object} superClass + * The class to inherit from + * + * @param {Object} [subClassMethods={}] + * The class to inherit to + * + * @return {Object} + * The new object with subClassMethods that inherited superClass. + */ +var extendFn = function extendFn(superClass) { + var subClassMethods = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + var subClass = function subClass() { + superClass.apply(this, arguments); + }; + + var methods = {}; + + if ((typeof subClassMethods === 'undefined' ? 'undefined' : _typeof(subClassMethods)) === 'object') { + if (subClassMethods.constructor !== Object.prototype.constructor) { + subClass = subClassMethods.constructor; + } + methods = subClassMethods; + } else if (typeof subClassMethods === 'function') { + subClass = subClassMethods; + } + + _inherits(subClass, superClass); + + // Extend subObj's prototype with functions and other properties from props + for (var name in methods) { + if (methods.hasOwnProperty(name)) { + subClass.prototype[name] = methods[name]; + } + } + + return subClass; +}; + +exports['default'] = extendFn; + +},{}],47:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _document = _dereq_(99); + +var _document2 = _interopRequireDefault(_document); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +/** + * Store the browser-specific methods for the fullscreen API. + * + * @type {Object} + * @see [Specification]{@link https://fullscreen.spec.whatwg.org} + * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js} + */ +var FullscreenApi = {}; + +// browser API methods +/** + * @file fullscreen-api.js + * @module fullscreen-api + * @private + */ +var apiMap = [['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']]; + +var specApi = apiMap[0]; +var browserApi = void 0; + +// determine the supported set of functions +for (var i = 0; i < apiMap.length; i++) { + // check for exitFullscreen function + if (apiMap[i][1] in _document2['default']) { + browserApi = apiMap[i]; + break; + } +} + +// map the browser API names to the spec API names +if (browserApi) { + for (var _i = 0; _i < browserApi.length; _i++) { + FullscreenApi[specApi[_i]] = browserApi[_i]; + } +} + +exports['default'] = FullscreenApi; + +},{"99":99}],48:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file loading-spinner.js + */ + + +/** + * A loading spinner for use during waiting/loading events. + * + * @extends Component + */ +var LoadingSpinner = function (_Component) { + _inherits(LoadingSpinner, _Component); + + function LoadingSpinner() { + _classCallCheck(this, LoadingSpinner); + + return _possibleConstructorReturn(this, _Component.apply(this, arguments)); + } + + /** + * Create the `LoadingSpinner`s DOM element. + * + * @return {Element} + * The dom element that gets created. + */ + LoadingSpinner.prototype.createEl = function createEl() { + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-loading-spinner', + dir: 'ltr' + }); + }; + + return LoadingSpinner; +}(_component2['default']); + +_component2['default'].registerComponent('LoadingSpinner', LoadingSpinner); +exports['default'] = LoadingSpinner; + +},{"5":5}],49:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _obj = _dereq_(93); + +/** + * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class. + * + * @param {number|string|Object|MediaError} value + * This can be of multiple types: + * - number: should be a standard error code + * - string: an error message (the code will be 0) + * - Object: arbitrary properties + * - `MediaError` (native): used to populate a video.js `MediaError` object + * - `MediaError` (video.js): will return itself if it's already a + * video.js `MediaError` object. + * + * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror} + * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes} + * + * @class MediaError + */ +function MediaError(value) { + + // Allow redundant calls to this constructor to avoid having `instanceof` + // checks peppered around the code. + if (value instanceof MediaError) { + return value; + } + + if (typeof value === 'number') { + this.code = value; + } else if (typeof value === 'string') { + // default code is zero, so this is a custom error + this.message = value; + } else if ((0, _obj.isObject)(value)) { + + // We assign the `code` property manually because native `MediaError` objects + // do not expose it as an own/enumerable property of the object. + if (typeof value.code === 'number') { + this.code = value.code; + } + + (0, _obj.assign)(this, value); + } + + if (!this.message) { + this.message = MediaError.defaultMessages[this.code] || ''; + } +} + +/** + * The error code that refers two one of the defined `MediaError` types + * + * @type {Number} + */ +/** + * @file media-error.js + */ +MediaError.prototype.code = 0; + +/** + * An optional message that to show with the error. Message is not part of the HTML5 + * video spec but allows for more informative custom errors. + * + * @type {String} + */ +MediaError.prototype.message = ''; + +/** + * An optional status code that can be set by plugins to allow even more detail about + * the error. For example a plugin might provide a specific HTTP status code and an + * error message for that code. Then when the plugin gets that error this class will + * know how to display an error message for it. This allows a custom message to show + * up on the `Player` error overlay. + * + * @type {Array} + */ +MediaError.prototype.status = null; + +/** + * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the + * specification listed under {@link MediaError} for more information. + * + * @enum {array} + * @readonly + * @property {string} 0 - MEDIA_ERR_CUSTOM + * @property {string} 1 - MEDIA_ERR_CUSTOM + * @property {string} 2 - MEDIA_ERR_ABORTED + * @property {string} 3 - MEDIA_ERR_NETWORK + * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED + * @property {string} 5 - MEDIA_ERR_ENCRYPTED + */ +MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED']; + +/** + * The default `MediaError` messages based on the {@link MediaError.errorTypes}. + * + * @type {Array} + * @constant + */ +MediaError.defaultMessages = { + 1: 'You aborted the media playback', + 2: 'A network error caused the media download to fail part-way.', + 3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.', + 4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.', + 5: 'The media is encrypted and we do not have the keys to decrypt it.' +}; + +// Add types as properties on MediaError +// e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4; +for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) { + MediaError[MediaError.errorTypes[errNum]] = errNum; + // values should be accessible on both the class and instance + MediaError.prototype[MediaError.errorTypes[errNum]] = errNum; +} + +// jsdocs for instance/static members added above +// instance methods use `#` and static methods use `.` +/** + * W3C error code for any custom error. + * + * @member MediaError#MEDIA_ERR_CUSTOM + * @constant {number} + * @default 0 + */ +/** + * W3C error code for any custom error. + * + * @member MediaError.MEDIA_ERR_CUSTOM + * @constant {number} + * @default 0 + */ + +/** + * W3C error code for media error aborted. + * + * @member MediaError#MEDIA_ERR_ABORTED + * @constant {number} + * @default 1 + */ +/** + * W3C error code for media error aborted. + * + * @member MediaError.MEDIA_ERR_ABORTED + * @constant {number} + * @default 1 + */ + +/** + * W3C error code for any network error. + * + * @member MediaError#MEDIA_ERR_NETWORK + * @constant {number} + * @default 2 + */ +/** + * W3C error code for any network error. + * + * @member MediaError.MEDIA_ERR_NETWORK + * @constant {number} + * @default 2 + */ + +/** + * W3C error code for any decoding error. + * + * @member MediaError#MEDIA_ERR_DECODE + * @constant {number} + * @default 3 + */ +/** + * W3C error code for any decoding error. + * + * @member MediaError.MEDIA_ERR_DECODE + * @constant {number} + * @default 3 + */ + +/** + * W3C error code for any time that a source is not supported. + * + * @member MediaError#MEDIA_ERR_SRC_NOT_SUPPORTED + * @constant {number} + * @default 4 + */ +/** + * W3C error code for any time that a source is not supported. + * + * @member MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED + * @constant {number} + * @default 4 + */ + +/** + * W3C error code for any time that a source is encrypted. + * + * @member MediaError#MEDIA_ERR_ENCRYPTED + * @constant {number} + * @default 5 + */ +/** + * W3C error code for any time that a source is encrypted. + * + * @member MediaError.MEDIA_ERR_ENCRYPTED + * @constant {number} + * @default 5 + */ + +exports['default'] = MediaError; + +},{"93":93}],50:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _button = _dereq_(2); + +var _button2 = _interopRequireDefault(_button); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _menu = _dereq_(52); + +var _menu2 = _interopRequireDefault(_menu); + +var _dom = _dereq_(85); + +var Dom = _interopRequireWildcard(_dom); + +var _fn = _dereq_(88); + +var Fn = _interopRequireWildcard(_fn); + +var _events = _dereq_(86); + +var Events = _interopRequireWildcard(_events); + +var _toTitleCase = _dereq_(96); + +var _toTitleCase2 = _interopRequireDefault(_toTitleCase); + +var _document = _dereq_(99); + +var _document2 = _interopRequireDefault(_document); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file menu-button.js + */ + + +/** + * A `MenuButton` class for any popup {@link Menu}. + * + * @extends Component + */ +var MenuButton = function (_Component) { + _inherits(MenuButton, _Component); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options={}] + * The key/value store of player options. + */ + function MenuButton(player) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + _classCallCheck(this, MenuButton); + + var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); + + _this.menuButton_ = new _button2['default'](player, options); + + _this.menuButton_.controlText(_this.controlText_); + _this.menuButton_.el_.setAttribute('aria-haspopup', 'true'); + + // Add buildCSSClass values to the button, not the wrapper + var buttonClass = _button2['default'].prototype.buildCSSClass(); + + _this.menuButton_.el_.className = _this.buildCSSClass() + ' ' + buttonClass; + _this.menuButton_.removeClass('vjs-control'); + + _this.addChild(_this.menuButton_); + + _this.update(); + + _this.enabled_ = true; + + _this.on(_this.menuButton_, 'tap', _this.handleClick); + _this.on(_this.menuButton_, 'click', _this.handleClick); + _this.on(_this.menuButton_, 'focus', _this.handleFocus); + _this.on(_this.menuButton_, 'blur', _this.handleBlur); + + _this.on('keydown', _this.handleSubmenuKeyPress); + return _this; + } + + /** + * Update the menu based on the current state of its items. + */ + + + MenuButton.prototype.update = function update() { + var menu = this.createMenu(); + + if (this.menu) { + this.removeChild(this.menu); + } + + this.menu = menu; + this.addChild(menu); + + /** + * Track the state of the menu button + * + * @type {Boolean} + * @private + */ + this.buttonPressed_ = false; + this.menuButton_.el_.setAttribute('aria-expanded', 'false'); + + if (this.items && this.items.length <= this.hideThreshold_) { + this.hide(); + } else { + this.show(); + } + }; + + /** + * Create the menu and add all items to it. + * + * @return {Menu} + * The constructed menu + */ + + + MenuButton.prototype.createMenu = function createMenu() { + var menu = new _menu2['default'](this.player_, { menuButton: this }); + + /** + * Hide the menu if the number of items is less than or equal to this threshold. This defaults + * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list + * it here because every time we run `createMenu` we need to reset the value. + * + * @protected + * @type {Number} + */ + this.hideThreshold_ = 0; + + // Add a title list item to the top + if (this.options_.title) { + var title = Dom.createEl('li', { + className: 'vjs-menu-title', + innerHTML: (0, _toTitleCase2['default'])(this.options_.title), + tabIndex: -1 + }); + + this.hideThreshold_ += 1; + + menu.children_.unshift(title); + Dom.prependTo(title, menu.contentEl()); + } + + this.items = this.createItems(); + + if (this.items) { + // Add menu items to the menu + for (var i = 0; i < this.items.length; i++) { + menu.addItem(this.items[i]); + } + } + + return menu; + }; + + /** + * Create the list of menu items. Specific to each subclass. + * + * @abstract + */ + + + MenuButton.prototype.createItems = function createItems() {}; + + /** + * Create the `MenuButtons`s DOM element. + * + * @return {Element} + * The element that gets created. + */ + + + MenuButton.prototype.createEl = function createEl() { + return _Component.prototype.createEl.call(this, 'div', { + className: this.buildWrapperCSSClass() + }, {}); + }; + + /** + * Allow sub components to stack CSS class names for the wrapper element + * + * @return {string} + * The constructed wrapper DOM `className` + */ + + + MenuButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { + 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'; + } + + // TODO: Fix the CSS so that this isn't necessary + var buttonClass = _button2['default'].prototype.buildCSSClass(); + + return 'vjs-menu-button ' + menuButtonClass + ' ' + buttonClass + ' ' + _Component.prototype.buildCSSClass.call(this); + }; + + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ + + + MenuButton.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 + ' ' + _Component.prototype.buildCSSClass.call(this); + }; + + /** + * Get or set the localized control text that will be used for accessibility. + * + * > NOTE: This will come from the internal `menuButton_` element. + * + * @param {string} [text] + * Control text for element. + * + * @param {Element} [el=this.menuButton_.el()] + * Element to set the title on. + * + * @return {string} + * - The control text when getting + */ + + + MenuButton.prototype.controlText = function controlText(text) { + var el = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.menuButton_.el(); + + return this.menuButton_.controlText(text, el); + }; + + /** + * Handle a click on a `MenuButton`. + * See {@link ClickableComponent#handleClick} for instances where this is called. + * + * @param {EventTarget~Event} event + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click + */ + + + MenuButton.prototype.handleClick = function handleClick(event) { + // When you click the button it adds focus, which will show the menu. + // So we'll remove focus when the mouse leaves the button. Focus is needed + // for tab navigation. + + this.one(this.menu.contentEl(), 'mouseleave', Fn.bind(this, function (e) { + this.unpressButton(); + this.el_.blur(); + })); + if (this.buttonPressed_) { + this.unpressButton(); + } else { + this.pressButton(); + } + }; + + /** + * Set the focus to the actual button, not to this element + */ + + + MenuButton.prototype.focus = function focus() { + this.menuButton_.focus(); + }; + + /** + * Remove the focus from the actual button, not this element + */ + + + MenuButton.prototype.blur = function blur() { + this.menuButton_.blur(); + }; + + /** + * This gets called when a `MenuButton` gains focus via a `focus` event. + * Turns on listening for `keydown` events. When they happen it + * calls `this.handleKeyPress`. + * + * @param {EventTarget~Event} event + * The `focus` event that caused this function to be called. + * + * @listens focus + */ + + + MenuButton.prototype.handleFocus = function handleFocus() { + Events.on(_document2['default'], 'keydown', Fn.bind(this, this.handleKeyPress)); + }; + + /** + * Called when a `MenuButton` loses focus. Turns off the listener for + * `keydown` events. Which Stops `this.handleKeyPress` from getting called. + * + * @param {EventTarget~Event} event + * The `blur` event that caused this function to be called. + * + * @listens blur + */ + + + MenuButton.prototype.handleBlur = function handleBlur() { + Events.off(_document2['default'], 'keydown', Fn.bind(this, this.handleKeyPress)); + }; + + /** + * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See + * {@link ClickableComponent#handleKeyPress} for instances where this is called. + * + * @param {EventTarget~Event} event + * The `keydown` event that caused this function to be called. + * + * @listens keydown + */ + + + MenuButton.prototype.handleKeyPress = function handleKeyPress(event) { + + // Escape (27) key or Tab (9) key unpress the 'button' + if (event.which === 27 || event.which === 9) { + if (this.buttonPressed_) { + this.unpressButton(); + } + // Don't preventDefault for Tab key - we still want to lose focus + if (event.which !== 9) { + event.preventDefault(); + // Set focus back to the menu button's button + this.menuButton_.el_.focus(); + } + // Up (38) key or Down (40) key press the 'button' + } else if (event.which === 38 || event.which === 40) { + if (!this.buttonPressed_) { + this.pressButton(); + event.preventDefault(); + } + } + }; + + /** + * Handle a `keydown` event on a sub-menu. The listener for this is added in + * the constructor. + * + * @param {EventTarget~Event} event + * Key press event + * + * @listens keydown + */ + + + MenuButton.prototype.handleSubmenuKeyPress = function handleSubmenuKeyPress(event) { + + // Escape (27) key or Tab (9) key unpress the 'button' + if (event.which === 27 || event.which === 9) { + if (this.buttonPressed_) { + this.unpressButton(); + } + // Don't preventDefault for Tab key - we still want to lose focus + if (event.which !== 9) { + event.preventDefault(); + // Set focus back to the menu button's button + this.menuButton_.el_.focus(); + } + } + }; + + /** + * Put the current `MenuButton` into a pressed state. + */ + + + MenuButton.prototype.pressButton = function pressButton() { + if (this.enabled_) { + this.buttonPressed_ = true; + this.menu.lockShowing(); + this.menuButton_.el_.setAttribute('aria-expanded', 'true'); + // set the focus into the submenu + this.menu.focus(); + } + }; + + /** + * Take the current `MenuButton` out of a pressed state. + */ + + + MenuButton.prototype.unpressButton = function unpressButton() { + if (this.enabled_) { + this.buttonPressed_ = false; + this.menu.unlockShowing(); + this.menuButton_.el_.setAttribute('aria-expanded', 'false'); + } + }; + + /** + * Disable the `MenuButton`. Don't allow it to be clicked. + */ + + + MenuButton.prototype.disable = function disable() { + this.unpressButton(); + + this.enabled_ = false; + this.addClass('vjs-disabled'); + + this.menuButton_.disable(); + }; + + /** + * Enable the `MenuButton`. Allow it to be clicked. + */ + + + MenuButton.prototype.enable = function enable() { + this.enabled_ = true; + this.removeClass('vjs-disabled'); + + this.menuButton_.enable(); + }; + + return MenuButton; +}(_component2['default']); + +_component2['default'].registerComponent('MenuButton', MenuButton); +exports['default'] = MenuButton; + +},{"2":2,"5":5,"52":52,"85":85,"86":86,"88":88,"96":96,"99":99}],51:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _clickableComponent = _dereq_(3); + +var _clickableComponent2 = _interopRequireDefault(_clickableComponent); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _obj = _dereq_(93); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file menu-item.js + */ + + +/** + * The component for a menu item. `
  • ` + * + * @extends ClickableComponent + */ +var MenuItem = function (_ClickableComponent) { + _inherits(MenuItem, _ClickableComponent); + + /** + * Creates an instance of the this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options={}] + * The key/value store of player options. + * + */ + function MenuItem(player, options) { + _classCallCheck(this, MenuItem); + + var _this = _possibleConstructorReturn(this, _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'); + } + return _this; + } + + /** + * Create the `MenuItem's DOM element + * + * @param {string} [type=li] + * Element's node type, not actually used, always set to `li`. + * + * @param {Object} [props={}] + * An object of properties that should be set on the element + * + * @param {Object} [attrs={}] + * An object of attributes that should be set on the element + * + * @return {Element} + * The element that gets created. + */ + + + MenuItem.prototype.createEl = function createEl(type, props, attrs) { + // The control is textual, not just an icon + this.nonIconControl = true; + + return _ClickableComponent.prototype.createEl.call(this, 'li', (0, _obj.assign)({ + className: 'vjs-menu-item', + innerHTML: '' + this.localize(this.options_.label) + '', + tabIndex: -1 + }, props), attrs); + }; + + /** + * Any click on a `MenuItem` puts int into the selected state. + * See {@link ClickableComponent#handleClick} for instances where this is called. + * + * @param {EventTarget~Event} event + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click + */ + + + MenuItem.prototype.handleClick = function handleClick(event) { + this.selected(true); + }; + + /** + * Set the state for this menu item as selected or not. + * + * @param {boolean} selected + * if the menu item is selected or not + */ + + + 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; +}(_clickableComponent2['default']); + +_component2['default'].registerComponent('MenuItem', MenuItem); +exports['default'] = MenuItem; + +},{"3":3,"5":5,"93":93}],52:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _dom = _dereq_(85); + +var Dom = _interopRequireWildcard(_dom); + +var _fn = _dereq_(88); + +var Fn = _interopRequireWildcard(_fn); + +var _events = _dereq_(86); + +var Events = _interopRequireWildcard(_events); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file menu.js + */ + + +/** + * The Menu component is used to build popup menus, including subtitle and + * captions selection menus. + * + * @extends Component + */ +var Menu = function (_Component) { + _inherits(Menu, _Component); + + /** + * Create an instance of this class. + * + * @param {Player} player + * the player that this component should attach to + * + * @param {Object} [options] + * Object of option names and values + * + */ + function Menu(player, options) { + _classCallCheck(this, Menu); + + var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); + + if (options) { + _this.menuButton_ = options.menuButton; + } + + _this.focusedChild_ = -1; + + _this.on('keydown', _this.handleKeyPress); + return _this; + } + + /** + * Add a {@link MenuItem} to the menu. + * + * @param {Object|string} component + * The name or instance of the `MenuItem` to add. + * + */ + + + Menu.prototype.addItem = function addItem(component) { + this.addChild(component); + component.on('click', Fn.bind(this, function (event) { + // Unpress the associated MenuButton, and move focus back to it + if (this.menuButton_) { + this.menuButton_.unpressButton(); + + // don't focus menu button if item is a caption settings item + // because focus will move elsewhere and it logs an error on IE8 + if (component.name() !== 'CaptionSettingsMenuItem') { + this.menuButton_.focus(); + } + } + })); + }; + + /** + * Create the `Menu`s DOM element. + * + * @return {Element} + * the element that was created + */ + + + 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.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 a `keydown` event on this menu. This listener is added in the constructor. + * + * @param {EventTarget~Event} event + * A `keydown` event that happened on the menu. + * + * @listens keydown + */ + + + Menu.prototype.handleKeyPress = function handleKeyPress(event) { + // Left and Down Arrows + if (event.which === 37 || event.which === 40) { + event.preventDefault(); + this.stepForward(); + + // Up and Right Arrows + } else if (event.which === 38 || event.which === 39) { + event.preventDefault(); + this.stepBack(); + } + }; + + /** + * Move to next (lower) menu item for keyboard users. + */ + + + 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. + */ + + + Menu.prototype.stepBack = function stepBack() { + var stepChild = 0; + + if (this.focusedChild_ !== undefined) { + stepChild = this.focusedChild_ - 1; + } + this.focus(stepChild); + }; + + /** + * Set focus on a {@link MenuItem} in the `Menu`. + * + * @param {Object|string} [item=0] + * Index of child item set focus on. + */ + + + Menu.prototype.focus = function focus() { + var item = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 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; +}(_component2['default']); + +_component2['default'].registerComponent('Menu', Menu); +exports['default'] = Menu; + +},{"5":5,"85":85,"86":86,"88":88}],53:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; +exports.isEvented = undefined; + +var _dom = _dereq_(85); + +var Dom = _interopRequireWildcard(_dom); + +var _events = _dereq_(86); + +var Events = _interopRequireWildcard(_events); + +var _fn = _dereq_(88); + +var Fn = _interopRequireWildcard(_fn); + +var _obj = _dereq_(93); + +var Obj = _interopRequireWildcard(_obj); + +var _eventTarget = _dereq_(45); + +var _eventTarget2 = _interopRequireDefault(_eventTarget); + +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; } } + +/** + * Returns whether or not an object has had the evented mixin applied. + * + * @param {Object} object + * An object to test. + * + * @return {boolean} + * Whether or not the object appears to be evented. + */ +var isEvented = function isEvented(object) { + return object instanceof _eventTarget2['default'] || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(function (k) { + return typeof object[k] === 'function'; + }); +}; + +/** + * Whether a value is a valid event type - non-empty string or array. + * + * @private + * @param {string|Array} type + * The type value to test. + * + * @return {boolean} + * Whether or not the type is a valid event type. + */ +/** + * @file mixins/evented.js + * @module evented + */ +var isValidEventType = function isValidEventType(type) { + return ( + // The regex here verifies that the `type` contains at least one non- + // whitespace character. + typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length + ); +}; + +/** + * Validates a value to determine if it is a valid event target. Throws if not. + * + * @private + * @throws {Error} + * If the target does not appear to be a valid event target. + * + * @param {Object} target + * The object to test. + */ +var validateTarget = function validateTarget(target) { + if (!target.nodeName && !isEvented(target)) { + throw new Error('Invalid target; must be a DOM node or evented object.'); + } +}; + +/** + * Validates a value to determine if it is a valid event target. Throws if not. + * + * @private + * @throws {Error} + * If the type does not appear to be a valid event type. + * + * @param {string|Array} type + * The type to test. + */ +var validateEventType = function validateEventType(type) { + if (!isValidEventType(type)) { + throw new Error('Invalid event type; must be a non-empty string or array.'); + } +}; + +/** + * Validates a value to determine if it is a valid listener. Throws if not. + * + * @private + * @throws {Error} + * If the listener is not a function. + * + * @param {Function} listener + * The listener to test. + */ +var validateListener = function validateListener(listener) { + if (typeof listener !== 'function') { + throw new Error('Invalid listener; must be a function.'); + } +}; + +/** + * Takes an array of arguments given to `on()` or `one()`, validates them, and + * normalizes them into an object. + * + * @private + * @param {Object} self + * The evented object on which `on()` or `one()` was called. This + * object will be bound as the `this` value for the listener. + * + * @param {Array} args + * An array of arguments passed to `on()` or `one()`. + * + * @return {Object} + * An object containing useful values for `on()` or `one()` calls. + */ +var normalizeListenArgs = function normalizeListenArgs(self, args) { + + // If the number of arguments is less than 3, the target is always the + // evented object itself. + var isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_; + var target = void 0; + var type = void 0; + var listener = void 0; + + if (isTargetingSelf) { + target = self.eventBusEl_; + + // Deal with cases where we got 3 arguments, but we are still listening to + // the evented object itself. + if (args.length >= 3) { + args.shift(); + } + + type = args[0]; + listener = args[1]; + } else { + target = args[0]; + type = args[1]; + listener = args[2]; + } + + validateTarget(target); + validateEventType(type); + validateListener(listener); + + listener = Fn.bind(self, listener); + + return { isTargetingSelf: isTargetingSelf, target: target, type: type, listener: listener }; +}; + +/** + * Adds the listener to the event type(s) on the target, normalizing for + * the type of target. + * + * @private + * @param {Element|Object} target + * A DOM node or evented object. + * + * @param {string} method + * The event binding method to use ("on" or "one"). + * + * @param {string|Array} type + * One or more event type(s). + * + * @param {Function} listener + * A listener function. + */ +var listen = function listen(target, method, type, listener) { + validateTarget(target); + + if (target.nodeName) { + Events[method](target, type, listener); + } else { + target[method](type, listener); + } +}; + +/** + * Contains methods that provide event capabilites to an object which is passed + * to {@link module:evented|evented}. + * + * @mixin EventedMixin + */ +var EventedMixin = { + + /** + * Add a listener to an event (or events) on this object or another evented + * object. + * + * @param {string|Array|Element|Object} targetOrType + * If this is a string or array, it represents the event type(s) + * that will trigger the listener. + * + * Another evented object can be passed here instead, which will + * cause the listener to listen for events on _that_ object. + * + * In either case, the listener's `this` value will be bound to + * this object. + * + * @param {string|Array|Function} typeOrListener + * If the first argument was a string or array, this should be the + * listener function. Otherwise, this is a string or array of event + * type(s). + * + * @param {Function} [listener] + * If the first argument was another evented object, this will be + * the listener function. + */ + on: function on() { + var _this = this; + + for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + var _normalizeListenArgs = normalizeListenArgs(this, args), + isTargetingSelf = _normalizeListenArgs.isTargetingSelf, + target = _normalizeListenArgs.target, + type = _normalizeListenArgs.type, + listener = _normalizeListenArgs.listener; + + listen(target, 'on', type, listener); + + // If this object is listening to another evented object. + if (!isTargetingSelf) { + + // If this object is disposed, remove the listener. + var removeListenerOnDispose = function removeListenerOnDispose() { + return _this.off(target, type, listener); + }; + + // Use the same function ID as the listener so we can remove it later it + // using the ID of the original listener. + removeListenerOnDispose.guid = listener.guid; + + // Add a listener to the target's dispose event as well. This ensures + // that if the target is disposed BEFORE this object, we remove the + // removal listener that was just added. Otherwise, we create a memory leak. + var removeRemoverOnTargetDispose = function removeRemoverOnTargetDispose() { + return _this.off('dispose', removeListenerOnDispose); + }; + + // Use the same function ID as the listener so we can remove it later + // it using the ID of the original listener. + removeRemoverOnTargetDispose.guid = listener.guid; + + listen(this, 'on', 'dispose', removeListenerOnDispose); + listen(target, 'on', 'dispose', removeRemoverOnTargetDispose); + } + }, + + + /** + * Add a listener to an event (or events) on this object or another evented + * object. The listener will only be called once and then removed. + * + * @param {string|Array|Element|Object} targetOrType + * If this is a string or array, it represents the event type(s) + * that will trigger the listener. + * + * Another evented object can be passed here instead, which will + * cause the listener to listen for events on _that_ object. + * + * In either case, the listener's `this` value will be bound to + * this object. + * + * @param {string|Array|Function} typeOrListener + * If the first argument was a string or array, this should be the + * listener function. Otherwise, this is a string or array of event + * type(s). + * + * @param {Function} [listener] + * If the first argument was another evented object, this will be + * the listener function. + */ + one: function one() { + var _this2 = this; + + for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + args[_key2] = arguments[_key2]; + } + + var _normalizeListenArgs2 = normalizeListenArgs(this, args), + isTargetingSelf = _normalizeListenArgs2.isTargetingSelf, + target = _normalizeListenArgs2.target, + type = _normalizeListenArgs2.type, + listener = _normalizeListenArgs2.listener; + + // Targeting this evented object. + + + if (isTargetingSelf) { + listen(target, 'one', type, listener); + + // Targeting another evented object. + } else { + var wrapper = function wrapper() { + for (var _len3 = arguments.length, largs = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { + largs[_key3] = arguments[_key3]; + } + + _this2.off(target, type, wrapper); + listener.apply(null, largs); + }; + + // Use the same function ID as the listener so we can remove it later + // it using the ID of the original listener. + wrapper.guid = listener.guid; + listen(target, 'one', type, wrapper); + } + }, + + + /** + * Removes listener(s) from event(s) on an evented object. + * + * @param {string|Array|Element|Object} [targetOrType] + * If this is a string or array, it represents the event type(s). + * + * Another evented object can be passed here instead, in which case + * ALL 3 arguments are _required_. + * + * @param {string|Array|Function} [typeOrListener] + * If the first argument was a string or array, this may be the + * listener function. Otherwise, this is a string or array of event + * type(s). + * + * @param {Function} [listener] + * If the first argument was another evented object, this will be + * the listener function; otherwise, _all_ listeners bound to the + * event type(s) will be removed. + */ + off: function off(targetOrType, typeOrListener, listener) { + + // Targeting this evented object. + if (!targetOrType || isValidEventType(targetOrType)) { + Events.off(this.eventBusEl_, targetOrType, typeOrListener); + + // Targeting another evented object. + } else { + var target = targetOrType; + var type = typeOrListener; + + // Fail fast and in a meaningful way! + validateTarget(target); + validateEventType(type); + validateListener(listener); + + // Ensure there's at least a guid, even if the function hasn't been used + listener = Fn.bind(this, listener); + + // Remove the dispose listener on this evented object, which was given + // the same guid as the event listener in on(). + this.off('dispose', listener); + + if (target.nodeName) { + Events.off(target, type, listener); + Events.off(target, 'dispose', listener); + } else if (isEvented(target)) { + target.off(type, listener); + target.off('dispose', listener); + } + } + }, + + + /** + * Fire an event on this evented object, causing its listeners to be called. + * + * @param {string|Object} event + * An event type or an object with a type property. + * + * @param {Object} [hash] + * An additional object to pass along to listeners. + * + * @returns {boolean} + * Whether or not the default behavior was prevented. + */ + trigger: function trigger(event, hash) { + return Events.trigger(this.eventBusEl_, event, hash); + } +}; + +/** + * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object. + * + * @param {Object} target + * The object to which to add event methods. + * + * @param {Object} [options={}] + * Options for customizing the mixin behavior. + * + * @param {String} [options.eventBusKey] + * By default, adds a `eventBusEl_` DOM element to the target object, + * which is used as an event bus. If the target object already has a + * DOM element that should be used, pass its key here. + * + * @return {Object} + * The target object. + */ +function evented(target) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + var eventBusKey = options.eventBusKey; + + // Set or create the eventBusEl_. + + if (eventBusKey) { + if (!target[eventBusKey].nodeName) { + throw new Error('The eventBusKey "' + eventBusKey + '" does not refer to an element.'); + } + target.eventBusEl_ = target[eventBusKey]; + } else { + target.eventBusEl_ = Dom.createEl('span', { className: 'vjs-event-bus' }); + } + + Obj.assign(target, EventedMixin); + + // When any evented object is disposed, it removes all its listeners. + target.on('dispose', function () { + return target.off(); + }); + + return target; +} + +exports['default'] = evented; +exports.isEvented = isEvented; + +},{"45":45,"85":85,"86":86,"88":88,"93":93}],54:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _evented = _dereq_(53); + +var _obj = _dereq_(93); + +var Obj = _interopRequireWildcard(_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; } } + +/** + * Contains methods that provide statefulness to an object which is passed + * to {@link module:stateful}. + * + * @mixin StatefulMixin + */ +/** + * @file mixins/stateful.js + * @module stateful + */ +var StatefulMixin = { + + /** + * A hash containing arbitrary keys and values representing the state of + * the object. + * + * @type {Object} + */ + state: {}, + + /** + * Set the state of an object by mutating its + * {@link module:stateful~StatefulMixin.state|state} object in place. + * + * @fires module:stateful~StatefulMixin#statechanged + * @param {Object|Function} stateUpdates + * A new set of properties to shallow-merge into the plugin state. + * Can be a plain object or a function returning a plain object. + * + * @returns {Object|undefined} + * An object containing changes that occurred. If no changes + * occurred, returns `undefined`. + */ + setState: function setState(stateUpdates) { + var _this = this; + + // Support providing the `stateUpdates` state as a function. + if (typeof stateUpdates === 'function') { + stateUpdates = stateUpdates(); + } + + var changes = void 0; + + Obj.each(stateUpdates, function (value, key) { + + // Record the change if the value is different from what's in the + // current state. + if (_this.state[key] !== value) { + changes = changes || {}; + changes[key] = { + from: _this.state[key], + to: value + }; + } + + _this.state[key] = value; + }); + + // Only trigger "statechange" if there were changes AND we have a trigger + // function. This allows us to not require that the target object be an + // evented object. + if (changes && (0, _evented.isEvented)(this)) { + + /** + * An event triggered on an object that is both + * {@link module:stateful|stateful} and {@link module:evented|evented} + * indicating that its state has changed. + * + * @event module:stateful~StatefulMixin#statechanged + * @type {Object} + * @property {Object} changes + * A hash containing the properties that were changed and + * the values they were changed `from` and `to`. + */ + this.trigger({ + changes: changes, + type: 'statechanged' + }); + } + + return changes; + } +}; + +/** + * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target + * object. + * + * If the target object is {@link module:evented|evented} and has a + * `handleStateChanged` method, that method will be automatically bound to the + * `statechanged` event on itself. + * + * @param {Object} target + * The object to be made stateful. + * + * @param {Object} [defaultState] + * A default set of properties to populate the newly-stateful object's + * `state` property. + * + * @returns {Object} + * Returns the `target`. + */ +function stateful(target, defaultState) { + Obj.assign(target, StatefulMixin); + + // This happens after the mixing-in because we need to replace the `state` + // added in that step. + target.state = Obj.assign({}, target.state, defaultState); + + // Auto-bind the `handleStateChanged` method of the target object if it exists. + if (typeof target.handleStateChanged === 'function' && (0, _evented.isEvented)(target)) { + target.on('statechanged', target.handleStateChanged); + } + + return target; +} + +exports['default'] = stateful; + +},{"53":53,"93":93}],55:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _dom = _dereq_(85); + +var Dom = _interopRequireWildcard(_dom); + +var _fn = _dereq_(88); + +var Fn = _interopRequireWildcard(_fn); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _window = _dereq_(100); + +var _window2 = _interopRequireDefault(_window); + +var _document = _dereq_(99); + +var _document2 = _interopRequireDefault(_document); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file modal-dialog.js + */ + + +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 + */ + +var ModalDialog = function (_Component) { + _inherits(ModalDialog, _Component); + + /** + * Create an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player 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); + + var _this = _possibleConstructorReturn(this, _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-control-text', + id: _this.el().getAttribute('aria-describedby') + }); + + Dom.textContent(_this.descEl_, _this.description()); + _this.el_.appendChild(_this.descEl_); + _this.el_.appendChild(_this.contentEl_); + return _this; + } + + /** + * Create the `ModalDialog`'s DOM element + * + * @return {Element} + * The DOM element that gets created. + */ + + + 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' + }); + }; + + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ + + + ModalDialog.prototype.buildCSSClass = function buildCSSClass() { + return MODAL_CLASS_NAME + ' vjs-hidden ' + _Component.prototype.buildCSSClass.call(this); + }; + + /** + * Handles `keydown` events on the document, looking for ESC, which closes + * the modal. + * + * @param {EventTarget~Event} e + * The keypress that triggered this event. + * + * @listens keydown + */ + + + 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} + * the localized or raw label of this modal. + */ + + + ModalDialog.prototype.label = function label() { + return this.localize(this.options_.label || 'Modal Window'); + }; + + /** + * Returns the description string for this modal. Primarily used for + * accessibility. + * + * @return {string} + * The localized or raw description of this modal. + */ + + + 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. + * + * @fires ModalDialog#beforemodalopen + * @fires ModalDialog#modalopen + */ + + + ModalDialog.prototype.open = function open() { + if (!this.opened_) { + var player = this.player(); + + /** + * Fired just before a `ModalDialog` is opened. + * + * @event ModalDialog#beforemodalopen + * @type {EventTarget~Event} + */ + 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.options_.pauseOnOpen && this.wasPlaying_) { + player.pause(); + } + + if (this.closeable()) { + this.on(this.el_.ownerDocument, 'keydown', Fn.bind(this, this.handleKeyPress)); + } + + player.controls(false); + this.show(); + this.conditionalFocus_(); + this.el().setAttribute('aria-hidden', 'false'); + + /** + * Fired just after a `ModalDialog` is opened. + * + * @event ModalDialog#modalopen + * @type {EventTarget~Event} + */ + this.trigger('modalopen'); + this.hasBeenOpened_ = true; + } + }; + + /** + * If the `ModalDialog` is currently open or closed. + * + * @param {boolean} [value] + * If given, it will open (`true`) or close (`false`) the modal. + * + * @return {boolean} + * the current open state of the modaldialog + */ + + + ModalDialog.prototype.opened = function opened(value) { + if (typeof value === 'boolean') { + this[value ? 'open' : 'close'](); + } + return this.opened_; + }; + + /** + * Closes the modal, does nothing if the `ModalDialog` is + * not open. + * + * @fires ModalDialog#beforemodalclose + * @fires ModalDialog#modalclose + */ + + + ModalDialog.prototype.close = function close() { + if (!this.opened_) { + return; + } + var player = this.player(); + + /** + * Fired just before a `ModalDialog` is closed. + * + * @event ModalDialog#beforemodalclose + * @type {EventTarget~Event} + */ + this.trigger('beforemodalclose'); + this.opened_ = false; + + if (this.wasPlaying_ && this.options_.pauseOnOpen) { + 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'); + + /** + * Fired just after a `ModalDialog` is closed. + * + * @event ModalDialog#modalclose + * @type {EventTarget~Event} + */ + this.trigger('modalclose'); + this.conditionalBlur_(); + + if (this.options_.temporary) { + this.dispose(); + } + }; + + /** + * Check to see if the `ModalDialog` is closeable via the UI. + * + * @param {boolean} [value] + * If given as a boolean, it will set the `closeable` option. + * + * @return {boolean} + * Returns the final value of the closable option. + */ + + + 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', { controlText: 'Close Modal Dialog' }); + 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. + */ + + + ModalDialog.prototype.fill = function fill() { + this.fillWith(this.content()); + }; + + /** + * Fill the modal's content element with arbitrary content. + * The content element will be emptied before this change takes place. + * + * @fires ModalDialog#beforemodalfill + * @fires ModalDialog#modalfill + * + * @param {Mixed} [content] + * The same rules apply to this as apply to the `content` option. + */ + + + ModalDialog.prototype.fillWith = function fillWith(content) { + var contentEl = this.contentEl(); + var parentEl = contentEl.parentNode; + var nextSiblingEl = contentEl.nextSibling; + + /** + * Fired just before a `ModalDialog` is filled with content. + * + * @event ModalDialog#beforemodalfill + * @type {EventTarget~Event} + */ + 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); + /** + * Fired just after a `ModalDialog` is filled with content. + * + * @event ModalDialog#modalfill + * @type {EventTarget~Event} + */ + this.trigger('modalfill'); + + // Re-inject the re-filled content element. + if (nextSiblingEl) { + parentEl.insertBefore(contentEl, nextSiblingEl); + } else { + parentEl.appendChild(contentEl); + } + + // make sure that the close button is last in the dialog DOM + var closeButton = this.getChild('closeButton'); + + if (closeButton) { + parentEl.appendChild(closeButton.el_); + } + }; + + /** + * Empties the content element. This happens anytime the modal is filled. + * + * @fires ModalDialog#beforemodalempty + * @fires ModalDialog#modalempty + */ + + + ModalDialog.prototype.empty = function empty() { + /** + * Fired just before a `ModalDialog` is emptied. + * + * @event ModalDialog#beforemodalempty + * @type {EventTarget~Event} + */ + this.trigger('beforemodalempty'); + Dom.emptyEl(this.contentEl()); + + /** + * Fired just after a `ModalDialog` is emptied. + * + * @event ModalDialog#modalempty + * @type {EventTarget~Event} + */ + this.trigger('modalempty'); + }; + + /** + * 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. + * + * @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} + * The current content of the modal dialog + */ + + + ModalDialog.prototype.content = function content(value) { + if (typeof value !== 'undefined') { + this.content_ = value; + } + return this.content_; + }; + + /** + * conditionally focus the modal dialog if focus was previously on the player. + * + * @private + */ + + + ModalDialog.prototype.conditionalFocus_ = function conditionalFocus_() { + var activeEl = _document2['default'].activeElement; + var playerEl = this.player_.el_; + + this.previouslyActiveEl_ = null; + + if (playerEl.contains(activeEl) || playerEl === activeEl) { + this.previouslyActiveEl_ = activeEl; + + this.focus(); + + this.on(_document2['default'], 'keydown', this.handleKeyDown); + } + }; + + /** + * conditionally blur the element and refocus the last focused element + * + * @private + */ + + + ModalDialog.prototype.conditionalBlur_ = function conditionalBlur_() { + if (this.previouslyActiveEl_) { + this.previouslyActiveEl_.focus(); + this.previouslyActiveEl_ = null; + } + + this.off(_document2['default'], 'keydown', this.handleKeyDown); + }; + + /** + * Keydown handler. Attached when modal is focused. + * + * @listens keydown + */ + + + ModalDialog.prototype.handleKeyDown = function handleKeyDown(event) { + // exit early if it isn't a tab key + if (event.which !== 9) { + return; + } + + var focusableEls = this.focusableEls_(); + var activeEl = this.el_.querySelector(':focus'); + var focusIndex = void 0; + + for (var i = 0; i < focusableEls.length; i++) { + if (activeEl === focusableEls[i]) { + focusIndex = i; + break; + } + } + + if (_document2['default'].activeElement === this.el_) { + focusIndex = 0; + } + + if (event.shiftKey && focusIndex === 0) { + focusableEls[focusableEls.length - 1].focus(); + event.preventDefault(); + } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) { + focusableEls[0].focus(); + event.preventDefault(); + } + }; + + /** + * get all focusable elements + * + * @private + */ + + + ModalDialog.prototype.focusableEls_ = function focusableEls_() { + var allChildren = this.el_.querySelectorAll('*'); + + return Array.prototype.filter.call(allChildren, function (child) { + return (child instanceof _window2['default'].HTMLAnchorElement || child instanceof _window2['default'].HTMLAreaElement) && child.hasAttribute('href') || (child instanceof _window2['default'].HTMLInputElement || child instanceof _window2['default'].HTMLSelectElement || child instanceof _window2['default'].HTMLTextAreaElement || child instanceof _window2['default'].HTMLButtonElement) && !child.hasAttribute('disabled') || child instanceof _window2['default'].HTMLIFrameElement || child instanceof _window2['default'].HTMLObjectElement || child instanceof _window2['default'].HTMLEmbedElement || child.hasAttribute('tabindex') && child.getAttribute('tabindex') !== -1 || child.hasAttribute('contenteditable'); + }); + }; + + return ModalDialog; +}(_component2['default']); + +/** + * Default options for `ModalDialog` default options. + * + * @type {Object} + * @private + */ + + +ModalDialog.prototype.options_ = { + pauseOnOpen: true, + temporary: true +}; + +_component2['default'].registerComponent('ModalDialog', ModalDialog); +exports['default'] = ModalDialog; + +},{"100":100,"5":5,"85":85,"88":88,"99":99}],56:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _templateObject = _taggedTemplateLiteralLoose(['\n Using the tech directly can be dangerous. I hope you know what you\'re doing.\n See https://github.com/videojs/video.js/issues/2617 for more info.\n '], ['\n Using the tech directly can be dangerous. I hope you know what you\'re doing.\n See https://github.com/videojs/video.js/issues/2617 for more info.\n ']); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _document = _dereq_(99); + +var _document2 = _interopRequireDefault(_document); + +var _window = _dereq_(100); + +var _window2 = _interopRequireDefault(_window); + +var _tsml = _dereq_(103); + +var _tsml2 = _interopRequireDefault(_tsml); + +var _evented = _dereq_(53); + +var _evented2 = _interopRequireDefault(_evented); + +var _events = _dereq_(86); + +var Events = _interopRequireWildcard(_events); + +var _dom = _dereq_(85); + +var Dom = _interopRequireWildcard(_dom); + +var _fn = _dereq_(88); + +var Fn = _interopRequireWildcard(_fn); + +var _guid = _dereq_(90); + +var Guid = _interopRequireWildcard(_guid); + +var _browser = _dereq_(81); + +var browser = _interopRequireWildcard(_browser); + +var _log = _dereq_(91); + +var _log2 = _interopRequireDefault(_log); + +var _toTitleCase = _dereq_(96); + +var _toTitleCase2 = _interopRequireDefault(_toTitleCase); + +var _timeRanges = _dereq_(95); + +var _buffer = _dereq_(82); + +var _stylesheet = _dereq_(94); + +var stylesheet = _interopRequireWildcard(_stylesheet); + +var _fullscreenApi = _dereq_(47); + +var _fullscreenApi2 = _interopRequireDefault(_fullscreenApi); + +var _mediaError = _dereq_(49); + +var _mediaError2 = _interopRequireDefault(_mediaError); + +var _tuple = _dereq_(102); + +var _tuple2 = _interopRequireDefault(_tuple); + +var _obj = _dereq_(93); + +var _mergeOptions = _dereq_(92); + +var _mergeOptions2 = _interopRequireDefault(_mergeOptions); + +var _textTrackListConverter = _dereq_(71); + +var _textTrackListConverter2 = _interopRequireDefault(_textTrackListConverter); + +var _modalDialog = _dereq_(55); + +var _modalDialog2 = _interopRequireDefault(_modalDialog); + +var _tech = _dereq_(64); + +var _tech2 = _interopRequireDefault(_tech); + +var _middleware = _dereq_(63); + +var middleware = _interopRequireWildcard(_middleware); + +var _trackTypes = _dereq_(77); + +var _filterSource = _dereq_(87); + +var _filterSource2 = _interopRequireDefault(_filterSource); + +_dereq_(62); + +_dereq_(58); + +_dereq_(70); + +_dereq_(48); + +_dereq_(1); + +_dereq_(4); + +_dereq_(8); + +_dereq_(44); + +_dereq_(73); + +_dereq_(61); + +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; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file player.js + */ +// Subclasses Component + + +// The following imports are used only to ensure that the corresponding modules +// are always included in the video.js package. Importing the modules will +// execute them and they will register themselves with video.js. + + +// Import Html5 tech, at least for disposing the original video tag. + + +// The following tech events are simply re-triggered +// on the player when they happen +var TECH_EVENTS_RETRIGGER = [ +/** + * Fired while the user agent is downloading media data. + * + * @event Player#progress + * @type {EventTarget~Event} + */ +/** + * Retrigger the `progress` event that was triggered by the {@link Tech}. + * + * @private + * @method Player#handleTechProgress_ + * @fires Player#progress + * @listens Tech#progress + */ +'progress', + +/** + * Fires when the loading of an audio/video is aborted. + * + * @event Player#abort + * @type {EventTarget~Event} + */ +/** + * Retrigger the `abort` event that was triggered by the {@link Tech}. + * + * @private + * @method Player#handleTechAbort_ + * @fires Player#abort + * @listens Tech#abort + */ +'abort', + +/** + * Fires when the browser is intentionally not getting media data. + * + * @event Player#suspend + * @type {EventTarget~Event} + */ +/** + * Retrigger the `suspend` event that was triggered by the {@link Tech}. + * + * @private + * @method Player#handleTechSuspend_ + * @fires Player#suspend + * @listens Tech#suspend + */ +'suspend', + +/** + * Fires when the current playlist is empty. + * + * @event Player#emptied + * @type {EventTarget~Event} + */ +/** + * Retrigger the `emptied` event that was triggered by the {@link Tech}. + * + * @private + * @method Player#handleTechEmptied_ + * @fires Player#emptied + * @listens Tech#emptied + */ +'emptied', +/** + * Fires when the browser is trying to get media data, but data is not available. + * + * @event Player#stalled + * @type {EventTarget~Event} + */ +/** + * Retrigger the `stalled` event that was triggered by the {@link Tech}. + * + * @private + * @method Player#handleTechStalled_ + * @fires Player#stalled + * @listens Tech#stalled + */ +'stalled', + +/** + * Fires when the browser has loaded meta data for the audio/video. + * + * @event Player#loadedmetadata + * @type {EventTarget~Event} + */ +/** + * Retrigger the `stalled` event that was triggered by the {@link Tech}. + * + * @private + * @method Player#handleTechLoadedmetadata_ + * @fires Player#loadedmetadata + * @listens Tech#loadedmetadata + */ +'loadedmetadata', + +/** + * Fires when the browser has loaded the current frame of the audio/video. + * + * @event player#loadeddata + * @type {event} + */ +/** + * Retrigger the `loadeddata` event that was triggered by the {@link Tech}. + * + * @private + * @method Player#handleTechLoaddeddata_ + * @fires Player#loadeddata + * @listens Tech#loadeddata + */ +'loadeddata', + +/** + * Fires when the current playback position has changed. + * + * @event player#timeupdate + * @type {event} + */ +/** + * Retrigger the `timeupdate` event that was triggered by the {@link Tech}. + * + * @private + * @method Player#handleTechTimeUpdate_ + * @fires Player#timeupdate + * @listens Tech#timeupdate + */ +'timeupdate', + +/** + * Fires when the playing speed of the audio/video is changed + * + * @event player#ratechange + * @type {event} + */ +/** + * Retrigger the `ratechange` event that was triggered by the {@link Tech}. + * + * @private + * @method Player#handleTechRatechange_ + * @fires Player#ratechange + * @listens Tech#ratechange + */ +'ratechange', + +/** + * Fires when the video's intrinsic dimensions change + * + * @event Player#resize + * @type {event} + */ +/** + * Retrigger the `resize` event that was triggered by the {@link Tech}. + * + * @private + * @method Player#handleTechResize_ + * @fires Player#resize + * @listens Tech#resize + */ +'resize', + +/** + * Fires when the volume has been changed + * + * @event player#volumechange + * @type {event} + */ +/** + * Retrigger the `volumechange` event that was triggered by the {@link Tech}. + * + * @private + * @method Player#handleTechVolumechange_ + * @fires Player#volumechange + * @listens Tech#volumechange + */ +'volumechange', + +/** + * Fires when the text track has been changed + * + * @event player#texttrackchange + * @type {event} + */ +/** + * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}. + * + * @private + * @method Player#handleTechTexttrackchange_ + * @fires Player#texttrackchange + * @listens Tech#texttrackchange + */ +'texttrackchange']; + +/** + * An instance of the `Player` class is created when any of the Video.js setup methods + * are used to initialize a video. + * + * After an instance has been created it can be accessed globally in two ways: + * 1. By calling `videojs('example_video_1');` + * 2. By using it directly via `videojs.players.example_video_1;` + * + * @extends Component + */ + +var Player = function (_Component) { + _inherits(Player, _Component); + + /** + * Create an instance of this class. + * + * @param {Element} tag + * The original video DOM element used for configuring options. + * + * @param {Object} [options] + * Object of option names and values. + * + * @param {Component~ReadyCallback} [ready] + * Ready callback function. + */ + function Player(tag, options, ready) { + _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 = (0, _obj.assign)(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; + + // If language is not set, get the closest lang attribute + if (!options.language) { + if (typeof tag.closest === 'function') { + var closest = tag.closest('[lang]'); + + if (closest) { + options.language = closest.getAttribute('lang'); + } + } else { + var element = tag; + + while (element && element.nodeType === 1) { + if (Dom.getAttributes(element).hasOwnProperty('lang')) { + options.language = element.getAttribute('lang'); + break; + } + element = element.parentNode; + } + } + } + + // Run base component initializing with new options + + // Turn off API access because we're loading a new tech that might load asynchronously + var _this = _possibleConstructorReturn(this, _Component.call(this, null, options, ready)); + + _this.isReady_ = false; + + // 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?'); + } + + // Store the original tag used to set options + _this.tag = tag; + + // Store the tag attributes used to restore html5 element + _this.tagAttributes = tag && Dom.getAttributes(tag); + + // Update current language + _this.language(_this.options_.language); + + // Update Supported Languages + if (options.languages) { + // 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; + + // Set default values for lastVolume + _this.cache_.lastVolume = 1; + + // 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(); + + // Make this an evented object and use `el_` as its event bus. + (0, _evented2['default'])(_this, { eventBusKey: 'el_' }); + + // 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 = (0, _mergeOptions2['default'])(_this.options_); + + // Load plugins + if (options.plugins) { + var plugins = options.plugins; + + Object.keys(plugins).forEach(function (name) { + if (typeof this[name] === 'function') { + this[name](plugins[name]); + } else { + throw new Error('plugin "' + name + '" does not exist'); + } + }, _this); + } + + _this.options_.playerOptions = playerOptionsCopy; + + _this.middleware_ = []; + + _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', _this.localize('Audio Player')); + } else { + _this.el_.setAttribute('aria-label', _this.localize('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; + + // Add a major version class to aid css in plugins + var majorVersion = '6.1.0'.split('.')[0]; + + _this.addClass('vjs-v' + majorVersion); + + // 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_); + + _this.changingSrc_ = false; + return _this; + } + + /** + * Destroys the video player and does any necessary cleanup. + * + * This is especially helpful if you are dynamically adding and removing videos + * to/from the DOM. + * + * @fires Player#dispose + */ + + + Player.prototype.dispose = function dispose() { + /** + * Called when the player is being disposed of. + * + * @event Player#dispose + * @type {EventTarget~Event} + */ + 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 `Player`'s DOM element. + * + * @return {Element} + * The DOM element that gets created. + */ + + + Player.prototype.createEl = function createEl() { + var tag = this.tag; + var el = void 0; + var playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player'); + + if (playerElIngest) { + el = this.el_ = tag.parentNode; + } else { + el = this.el_ = _Component.prototype.createEl.call(this, 'div'); + } + + // set tabindex to -1 so we could focus on the player element + tag.setAttribute('tabindex', '-1'); + + // 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.getAttributes(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 (_window2['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.addClass(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 && !playerElIngest) { + 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 + // + // Breaks iPhone, fixed in HTML5 setup. + Dom.prependTo(tag, el); + this.children_.unshift(tag); + + // Set lang attr on player to ensure CSS :lang() in consistent with player + // if it's been set to something different to the doc + this.el_.setAttribute('lang', this.language_); + + this.el_ = el; + + return el; + }; + + /** + * A getter/setter for the `Player`'s width. + * + * @param {number} [value] + * The value to set the `Player's width to. + * + * @return {number} + * The current width of the `Player` when getting. + */ + + + Player.prototype.width = function width(value) { + return this.dimension('width', value); + }; + + /** + * A getter/setter for the `Player`'s height. + * + * @param {number} [value] + * The value to set the `Player's heigth to. + * + * @return {number} + * The current height of the `Player` when getting. + */ + + + Player.prototype.height = function height(value) { + return this.dimension('height', value); + }; + + /** + * A getter/setter for the `Player`'s width & height. + * + * @param {string} dimension + * This string can be: + * - 'width' + * - 'height' + * + * @param {number} [value] + * Value for dimension specified in the first argument. + * + * @return {number} + * The dimension arguments value when getting (width/height). + */ + + + 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)) { + _log2['default'].error('Improper value "' + value + '" supplied for for ' + _dimension); + return; + } + + this[privDimension] = parsedVal; + } + + this.updateStyleEl_(); + }; + + /** + * A getter/setter/toggler for the vjs-fluid `className` on the `Player`. + * + * @param {boolean} [bool] + * - A value of true adds the class. + * - A value of false removes the class. + * - No value will toggle the fluid class. + * + * @return {boolean|undefined} + * - The value of fluid when getting. + * - `undefined` when setting. + */ + + + 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'); + } + + this.updateStyleEl_(); + }; + + /** + * Get/Set the aspect ratio + * + * @param {string} [ratio] + * Aspect ratio for player + * + * @return {string|undefined} + * returns the current aspect ratio when getting + */ + + /** + * A getter/setter for the `Player`'s aspect ratio. + * + * @param {string} [ratio] + * The value to set the `Player's aspect ratio to. + * + * @return {string|undefined} + * - The current aspect ratio of the `Player` when getting. + * - undefined when setting + */ + + + 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). + * + * @private + * @listens Tech#loadedmetadata + */ + + + Player.prototype.updateStyleEl_ = function updateStyleEl_() { + if (_window2['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 = void 0; + var height = void 0; + var aspectRatio = void 0; + var idClass = void 0; + + // 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() > 0) { + // 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/Create an instance of playback {@link Tech} including element + * and API methods. Then append the `Tech` element in `Player` as a child. + * + * @param {string} techName + * name of the playback technology + * + * @param {string} source + * video source + * + * @private + */ + + + Player.prototype.loadTech_ = function loadTech_(techName, source) { + var _this2 = this; + + // Pause and remove current playback technology + if (this.tech_) { + this.unloadTech_(); + } + + var titleTechName = (0, _toTitleCase2['default'])(techName); + var camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1); + + // get rid of the HTML5 video tag as soon as we are using another tech + if (titleTechName !== 'Html5' && this.tag) { + _tech2['default'].getTech('Html5').disposeMediaElement(this.tag); + this.tag.player = null; + this.tag = null; + } + + this.techName_ = titleTechName; + + // 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 = { + source: source, + 'nativeControlsForTouch': this.options_.nativeControlsForTouch, + 'playerId': this.id(), + 'techId': this.id() + '_' + titleTechName + '_api', + 'autoplay': this.options_.autoplay, + 'playsinline': this.options_.playsinline, + 'preload': this.options_.preload, + 'loop': this.options_.loop, + 'muted': this.options_.muted, + 'poster': this.poster(), + 'language': this.language(), + 'playerElIngest': this.playerElIngest_ || false, + 'vtt.js': this.options_['vtt.js'] + }; + + _trackTypes.ALL.names.forEach(function (name) { + var props = _trackTypes.ALL[name]; + + techOptions[props.getterName] = _this2[props.privateName]; + }); + + (0, _obj.assign)(techOptions, this.options_[titleTechName]); + (0, _obj.assign)(techOptions, this.options_[camelTechName]); + (0, _obj.assign)(techOptions, this.options_[techName.toLowerCase()]); + + if (this.tag) { + techOptions.tag = this.tag; + } + + if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) { + techOptions.startTime = this.cache_.currentTime; + } + + // Initialize tech instance + var TechClass = _tech2['default'].getTech(techName); + + if (!TechClass) { + throw new Error('No Tech named \'' + titleTechName + '\' exists! \'' + titleTechName + '\' should be registered using videojs.registerTech()\''); + } + + this.tech_ = new TechClass(techOptions); + + // player.triggerReady is always async, so don't need this to be async + this.tech_.ready(Fn.bind(this, this.handleTechReady_), true); + + _textTrackListConverter2['default'].jsonToTextTracks(this.textTracksJson_ || [], this.tech_); + + // Listen to all HTML5-defined events and trigger them on the player + TECH_EVENTS_RETRIGGER.forEach(function (event) { + _this2.on(_this2.tech_, event, _this2['handleTech' + (0, _toTitleCase2['default'])(event) + '_']); + }); + 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_, 'durationchange', this.handleTechDurationChange_); + this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_); + this.on(this.tech_, 'error', this.handleTechError_); + this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_); + this.on(this.tech_, 'posterchange', this.handleTechPosterChange_); + this.on(this.tech_, 'textdata', this.handleTechTextData_); + + 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() && (titleTechName !== 'Html5' || !this.tag)) { + Dom.prependTo(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 and dispose of the current playback {@link Tech}. + * + * @private + */ + + + Player.prototype.unloadTech_ = function unloadTech_() { + var _this3 = this; + + // Save the current text tracks so that we can reuse the same text tracks with the next tech + _trackTypes.ALL.names.forEach(function (name) { + var props = _trackTypes.ALL[name]; + + _this3[props.privateName] = _this3[props.getterName](); + }); + this.textTracksJson_ = _textTrackListConverter2['default'].textTracksToJson(this.tech_); + + this.isReady_ = false; + + this.tech_.dispose(); + + this.tech_ = false; + }; + + /** + * Return a reference to the current {@link Tech}. + * It will print a warning by default about the danger of using the tech directly + * but any argument that is passed in will silence the warning. + * + * @param {*} [safety] + * Anything passed in to silence the warning + * + * @return {Tech} + * The Tech + */ + + + Player.prototype.tech = function tech(safety) { + if (safety === undefined) { + _log2['default'].warn((0, _tsml2['default'])(_templateObject)); + } + + return this.tech_; + }; + + /** + * Set up click and touch listeners for the playback element + * + * - On desktops: a click on the video itself will toggle playback + * - On mobile devices: a click on the video toggles controls + * which 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 + */ + + + 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. + * + * @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 + * + * @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.currentSrc()) && this.tag && this.options_.autoplay && this.paused()) { + try { + // Chrome Fix. Fixed in Chrome v16. + delete this.tag.poster; + } catch (e) { + (0, _log2['default'])('deleting tag.poster throws in some browsers', e); + } + this.play(); + } + }; + + /** + * Retrigger the `loadstart` event that was triggered by the {@link Tech}. This + * function will also trigger {@link Player#firstplay} if it is the first loadstart + * for a video. + * + * @fires Player#loadstart + * @fires Player#firstplay + * @listens Tech#loadstart + * @private + */ + + + Player.prototype.handleTechLoadStart_ = function handleTechLoadStart_() { + // TODO: Update to use `emptied` event instead. See #1277. + + this.removeClass('vjs-ended'); + this.removeClass('vjs-seeking'); + + // 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()) { + /** + * Fired when the user agent begins looking for media data + * + * @event Player#loadstart + * @type {EventTarget~Event} + */ + this.trigger('loadstart'); + this.trigger('firstplay'); + } else { + // reset the hasStarted state + this.hasStarted(false); + this.trigger('loadstart'); + } + }; + + /** + * Add/remove the vjs-has-started class + * + * @fires Player#firstplay + * + * @param {boolean} hasStarted + * - true: adds the class + * - false: remove the class + * + * @return {boolean} + * the boolean value of 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; + } + return !!this.hasStarted_; + }; + + /** + * Fired whenever the media begins or resumes playback + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play} + * @fires Player#play + * @listens Tech#play + * @private + */ + + + 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 + this.hasStarted(true); + /** + * Triggered whenever an {@link Tech#play} event happens. Indicates that + * playback has started or resumed. + * + * @event Player#play + * @type {EventTarget~Event} + */ + this.trigger('play'); + }; + + /** + * Retrigger the `waiting` event that was triggered by the {@link Tech}. + * + * @fires Player#waiting + * @listens Tech#waiting + * @private + */ + + + Player.prototype.handleTechWaiting_ = function handleTechWaiting_() { + var _this4 = this; + + this.addClass('vjs-waiting'); + /** + * A readyState change on the DOM element has caused playback to stop. + * + * @event Player#waiting + * @type {EventTarget~Event} + */ + this.trigger('waiting'); + this.one('timeupdate', function () { + return _this4.removeClass('vjs-waiting'); + }); + }; + + /** + * Retrigger the `canplay` event that was triggered by the {@link Tech}. + * > Note: This is not consistent between browsers. See #1351 + * + * @fires Player#canplay + * @listens Tech#canplay + * @private + */ + + + Player.prototype.handleTechCanPlay_ = function handleTechCanPlay_() { + this.removeClass('vjs-waiting'); + /** + * The media has a readyState of HAVE_FUTURE_DATA or greater. + * + * @event Player#canplay + * @type {EventTarget~Event} + */ + this.trigger('canplay'); + }; + + /** + * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}. + * + * @fires Player#canplaythrough + * @listens Tech#canplaythrough + * @private + */ + + + Player.prototype.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() { + this.removeClass('vjs-waiting'); + /** + * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the + * entire media file can be played without buffering. + * + * @event Player#canplaythrough + * @type {EventTarget~Event} + */ + this.trigger('canplaythrough'); + }; + + /** + * Retrigger the `playing` event that was triggered by the {@link Tech}. + * + * @fires Player#playing + * @listens Tech#playing + * @private + */ + + + Player.prototype.handleTechPlaying_ = function handleTechPlaying_() { + this.removeClass('vjs-waiting'); + /** + * The media is no longer blocked from playback, and has started playing. + * + * @event Player#playing + * @type {EventTarget~Event} + */ + this.trigger('playing'); + }; + + /** + * Retrigger the `seeking` event that was triggered by the {@link Tech}. + * + * @fires Player#seeking + * @listens Tech#seeking + * @private + */ + + + Player.prototype.handleTechSeeking_ = function handleTechSeeking_() { + this.addClass('vjs-seeking'); + /** + * Fired whenever the player is jumping to a new time + * + * @event Player#seeking + * @type {EventTarget~Event} + */ + this.trigger('seeking'); + }; + + /** + * Retrigger the `seeked` event that was triggered by the {@link Tech}. + * + * @fires Player#seeked + * @listens Tech#seeked + * @private + */ + + + Player.prototype.handleTechSeeked_ = function handleTechSeeked_() { + this.removeClass('vjs-seeking'); + /** + * Fired when the player has finished jumping to a new time + * + * @event Player#seeked + * @type {EventTarget~Event} + */ + this.trigger('seeked'); + }; + + /** + * Retrigger the `firstplay` event that was triggered by the {@link Tech}. + * + * @fires Player#firstplay + * @listens Tech#firstplay + * @deprecated As of 6.0 firstplay event is deprecated. + * @deprecated As of 6.0 passing the `starttime` option to the player and the firstplay event are deprecated. + * @private + */ + + + 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) { + _log2['default'].warn('Passing the `starttime` option to the player will be deprecated in 6.0'); + this.currentTime(this.options_.starttime); + } + + this.addClass('vjs-has-started'); + /** + * Fired the first time a video is played. Not part of the HLS spec, and this is + * probably not the best implementation yet, so use sparingly. If you don't have a + * reason to prevent playback, use `myPlayer.one('play');` instead. + * + * @event Player#firstplay + * @deprecated As of 6.0 firstplay event is deprecated. + * @type {EventTarget~Event} + */ + this.trigger('firstplay'); + }; + + /** + * Retrigger the `pause` event that was triggered by the {@link Tech}. + * + * @fires Player#pause + * @listens Tech#pause + * @private + */ + + + Player.prototype.handleTechPause_ = function handleTechPause_() { + this.removeClass('vjs-playing'); + this.addClass('vjs-paused'); + /** + * Fired whenever the media has been paused + * + * @event Player#pause + * @type {EventTarget~Event} + */ + this.trigger('pause'); + }; + + /** + * Retrigger the `ended` event that was triggered by the {@link Tech}. + * + * @fires Player#ended + * @listens Tech#ended + * @private + */ + + + Player.prototype.handleTechEnded_ = function handleTechEnded_() { + this.addClass('vjs-ended'); + if (this.options_.loop) { + this.currentTime(0); + this.play(); + } else if (!this.paused()) { + this.pause(); + } + + /** + * Fired when the end of the media resource is reached (currentTime == duration) + * + * @event Player#ended + * @type {EventTarget~Event} + */ + this.trigger('ended'); + }; + + /** + * Fired when the duration of the media resource is first known or changed + * + * @listens Tech#durationchange + * @private + */ + + + Player.prototype.handleTechDurationChange_ = function handleTechDurationChange_() { + this.duration(this.techGet_('duration')); + }; + + /** + * Handle a click on the media element to play/pause + * + * @param {EventTarget~Event} event + * the event that caused this function to trigger + * + * @listens Tech#mousedown + * @private + */ + + + 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. + * + * @listens Tech#tap + * @private + */ + + + Player.prototype.handleTechTap_ = function handleTechTap_() { + this.userActive(!this.userActive()); + }; + + /** + * Handle touch to start + * + * @listens Tech#touchstart + * @private + */ + + + Player.prototype.handleTechTouchStart_ = function handleTechTouchStart_() { + this.userWasActive = this.userActive(); + }; + + /** + * Handle touch to move + * + * @listens Tech#touchmove + * @private + */ + + + Player.prototype.handleTechTouchMove_ = function handleTechTouchMove_() { + if (this.userWasActive) { + this.reportUserActivity(); + } + }; + + /** + * Handle touch to end + * + * @param {EventTarget~Event} event + * the touchend event that triggered + * this function + * + * @listens Tech#touchend + * @private + */ + + + 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 + * @listens Player#fullscreenchange + */ + + + 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 + * @listens stageclick + */ + + + Player.prototype.handleStageClick_ = function handleStageClick_() { + this.reportUserActivity(); + }; + + /** + * Handle Tech Fullscreen Change + * + * @param {EventTarget~Event} event + * the fullscreenchange event that triggered this function + * + * @param {Object} data + * the data that was sent with the event + * + * @private + * @listens Tech#fullscreenchange + * @fires Player#fullscreenchange + */ + + + Player.prototype.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) { + if (data) { + this.isFullscreen(data.isFullscreen); + } + /** + * Fired when going in and out of fullscreen. + * + * @event Player#fullscreenchange + * @type {EventTarget~Event} + */ + this.trigger('fullscreenchange'); + }; + + /** + * Fires when an error occurred during the loading of an audio/video. + * + * @private + * @listens Tech#error + */ + + + Player.prototype.handleTechError_ = function handleTechError_() { + var error = this.tech_.error(); + + this.error(error); + }; + + /** + * Retrigger the `textdata` event that was triggered by the {@link Tech}. + * + * @fires Player#textdata + * @listens Tech#textdata + * @private + */ + + + Player.prototype.handleTechTextData_ = function handleTechTextData_() { + var data = null; + + if (arguments.length > 1) { + data = arguments[1]; + } + + /** + * Fires when we get a textdata event from tech + * + * @event Player#textdata + * @type {EventTarget~Event} + */ + this.trigger('textdata', data); + }; + + /** + * Get object for cached values. + * + * @return {Object} + * get the current object cache + */ + + + Player.prototype.getCache = function getCache() { + return this.cache_; + }; + + /** + * Pass values to the playback tech + * + * @param {string} [method] + * the method to call + * + * @param {Object} arg + * the argument to pass + * + * @private + */ + + + Player.prototype.techCall_ = function techCall_(method, arg) { + // If it's not ready yet, call method when it is + + this.ready(function () { + if (method in middleware.allowedSetters) { + return middleware.set(this.middleware_, this.tech_, method, arg); + } + + try { + if (this.tech_) { + this.tech_[method](arg); + } + } catch (e) { + (0, _log2['default'])(e); + throw e; + } + }, true); + }; + + /** + * Get calls can't wait for the tech, and sometimes don't need to. + * + * @param {string} method + * Tech method + * + * @return {Function|undefined} + * the method or undefined + * + * @private + */ + + + Player.prototype.techGet_ = function techGet_(method) { + if (this.tech_ && this.tech_.isReady_) { + + if (method in middleware.allowedGetters) { + return middleware.get(this.middleware_, this.tech_, method); + } + + // 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) { + (0, _log2['default'])('Video.js: ' + method + ' method not defined for ' + this.techName_ + ' playback technology.', e); + + // When a method isn't available on the object it throws a TypeError + } else if (e.name === 'TypeError') { + (0, _log2['default'])('Video.js: ' + method + ' unavailable on ' + this.techName_ + ' playback technology element.', e); + this.tech_.isReady_ = false; + } else { + (0, _log2['default'])(e); + } + throw e; + } + } + + return; + }; + + /** + * start media playback + * + * @return {Promise|undefined} + * Returns a `Promise` if the browser returns one, for most browsers this will + * return undefined. + */ + + + Player.prototype.play = function play() { + if (this.changingSrc_) { + this.ready(function () { + var retval = this.techGet_('play'); + + // silence errors (unhandled promise from play) + if (retval !== undefined && typeof retval.then === 'function') { + retval.then(null, function (e) {}); + } + }); + + // Only calls the tech's play if we already have a src loaded + } else if (this.isReady_ && (this.src() || this.currentSrc())) { + return this.techGet_('play'); + } else { + this.ready(function () { + this.tech_.one('loadstart', function () { + var retval = this.play(); + + // silence errors (unhandled promise from play) + if (retval !== undefined && typeof retval.then === 'function') { + retval.then(null, function (e) {}); + } + }); + }); + } + }; + + /** + * Pause the video playback + * + * @return {Player} + * A reference to the player object this function was called on + */ + + + Player.prototype.pause = function pause() { + this.techCall_('pause'); + }; + + /** + * Check if the player is paused or has yet to play + * + * @return {boolean} + * - false: if the media is currently playing + * - true: if media is not currently playing + */ + + + 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; + }; + + /** + * Get a TimeRange object representing the current ranges of time that the user + * has played. + * + * @return {TimeRange} + * A time range object that represents all the increments of time that have + * been played. + */ + + + Player.prototype.played = function played() { + return this.techGet_('played') || (0, _timeRanges.createTimeRange)(0, 0); + }; + + /** + * 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] + * wether the user is or is not scrubbing + * + * @return {boolean} + * The value of scrubbing when getting + */ + + + Player.prototype.scrubbing = function scrubbing(isScrubbing) { + if (typeof isScrubbing === 'undefined') { + return this.scrubbing_; + } + this.scrubbing_ = !!isScrubbing; + + if (isScrubbing) { + this.addClass('vjs-scrubbing'); + } else { + this.removeClass('vjs-scrubbing'); + } + }; + + /** + * Get or set the current time (in seconds) + * + * @param {number|string} [seconds] + * The time to seek to in seconds + * + * @return {number} + * - the current time in seconds when getting + */ + + + Player.prototype.currentTime = function currentTime(seconds) { + if (typeof seconds !== 'undefined') { + this.techCall_('setCurrentTime', seconds); + return; + } + + // 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. + this.cache_.currentTime = this.techGet_('currentTime') || 0; + return this.cache_.currentTime; + }; + + /** + * Normally gets the length in time of the video in seconds; + * in all but the rarest use cases an argument will NOT be passed to the method + * + * > **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. + * + * @fires Player#durationchange + * + * @param {number} [seconds] + * The duration of the video to set in seconds + * + * @return {number} + * - The duration of the video in seconds when getting + */ + + + 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'); + } + /** + * @event Player#durationchange + * @type {EventTarget~Event} + */ + this.trigger('durationchange'); + } + }; + + /** + * Calculates how much time is left in the video. Not part + * of the native video API. + * + * @return {number} + * The time remaining in seconds + */ + + + Player.prototype.remainingTime = function remainingTime() { + return this.duration() - this.currentTime(); + }; + + // + // Kind of like an array of portions of the video that have been downloaded. + + /** + * Get a TimeRange object with an array of the times of the video + * that have been downloaded. If you just want the percent of the + * video that's been downloaded, use bufferedPercent. + * + * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered} + * + * @return {TimeRange} + * A mock TimeRange object (following HTML spec) + */ + + + Player.prototype.buffered = function buffered() { + var buffered = this.techGet_('buffered'); + + if (!buffered || !buffered.length) { + buffered = (0, _timeRanges.createTimeRange)(0, 0); + } + + return buffered; + }; + + /** + * Get the percent (as a decimal) of the video that's been downloaded. + * This method is not a part of the native HTML video API. + * + * @return {number} + * A decimal between 0 and 1 representing the percent + * that is bufferred 0 being 0% and 1 being 100% + */ + + + Player.prototype.bufferedPercent = function bufferedPercent() { + return (0, _buffer.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 + */ + + + Player.prototype.bufferedEnd = function bufferedEnd() { + var buffered = this.buffered(); + var duration = this.duration(); + var end = buffered.end(buffered.length - 1); + + if (end > duration) { + end = duration; + } + + return end; + }; + + /** + * Get or set the current volume of the media + * + * @param {number} [percentAsDecimal] + * The new volume as a decimal percent: + * - 0 is muted/0%/off + * - 1.0 is 100%/full + * - 0.5 is half volume or 50% + * + * @return {number} + * The current volume as a percent when getting + */ + + + Player.prototype.volume = function volume(percentAsDecimal) { + var vol = void 0; + + if (percentAsDecimal !== undefined) { + // Force value to between 0 and 1 + vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); + this.cache_.volume = vol; + this.techCall_('setVolume', vol); + + if (vol > 0) { + this.lastVolume_(vol); + } + + return; + } + + // 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 + * + * @param {boolean} [muted] + * - true to mute + * - false to unmute + * + * @return {boolean} + * - true if mute is on and getting + * - false if mute is off and getting + */ + + + Player.prototype.muted = function muted(_muted) { + if (_muted !== undefined) { + this.techCall_('setMuted', _muted); + return; + } + return this.techGet_('muted') || false; + }; + + /** + * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted + * indicates the state of muted on intial playback. + * + * ```js + * var myPlayer = videojs('some-player-id'); + * + * myPlayer.src("http://www.example.com/path/to/video.mp4"); + * + * // get, should be false + * console.log(myPlayer.defaultMuted()); + * // set to true + * myPlayer.defaultMuted(true); + * // get should be true + * console.log(myPlayer.defaultMuted()); + * ``` + * + * @param {boolean} [defaultMuted] + * - true to mute + * - false to unmute + * + * @return {boolean|Player} + * - true if defaultMuted is on and getting + * - false if defaultMuted is off and getting + * - A reference to the current player when setting + */ + + + Player.prototype.defaultMuted = function defaultMuted(_defaultMuted) { + if (_defaultMuted !== undefined) { + return this.techCall_('setDefaultMuted', _defaultMuted); + } + return this.techGet_('defaultMuted') || false; + }; + + /** + * Get the last volume, or set it + * + * @param {number} [percentAsDecimal] + * The new last volume as a decimal percent: + * - 0 is muted/0%/off + * - 1.0 is 100%/full + * - 0.5 is half volume or 50% + * + * @return {number} + * the current value of lastVolume as a percent when getting + * + * @private + */ + + + Player.prototype.lastVolume_ = function lastVolume_(percentAsDecimal) { + if (percentAsDecimal !== undefined && percentAsDecimal !== 0) { + this.cache_.lastVolume = percentAsDecimal; + return; + } + return this.cache_.lastVolume; + }; + + /** + * Check if current tech can support native fullscreen + * (e.g. with built in controls like iOS, so not our flash swf) + * + * @return {boolean} + * if native fullscreen is supported + */ + + + Player.prototype.supportsFullScreen = function supportsFullScreen() { + return this.techGet_('supportsFullScreen') || false; + }; + + /** + * Check if the player is in fullscreen mode or tell the player that it + * is or is not in fullscreen mode. + * + * > 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] + * Set the players current fullscreen state + * + * @return {boolean} + * - true if fullscreen is on and getting + * - false if fullscreen is off and getting + */ + + + Player.prototype.isFullscreen = function isFullscreen(isFS) { + if (isFS !== undefined) { + this.isFullscreen_ = !!isFS; + return; + } + return !!this.isFullscreen_; + }; + + /** + * Increase the size of the video to full screen + * 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. + * + * @fires Player#fullscreenchange + */ + + + Player.prototype.requestFullscreen = function requestFullscreen() { + var fsApi = _fullscreenApi2['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(_document2['default'], fsApi.fullscreenchange, Fn.bind(this, function documentFullscreenChange(e) { + this.isFullscreen(_document2['default'][fsApi.fullscreenElement]); + + // If cancelling fullscreen, remove event listener. + if (this.isFullscreen() === false) { + Events.off(_document2['default'], fsApi.fullscreenchange, documentFullscreenChange); + } + /** + * @event Player#fullscreenchange + * @type {EventTarget~Event} + */ + 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(); + /** + * @event Player#fullscreenchange + * @type {EventTarget~Event} + */ + this.trigger('fullscreenchange'); + } + }; + + /** + * Return the video to its normal size after having been in full screen mode + * + * @fires Player#fullscreenchange + */ + + + Player.prototype.exitFullscreen = function exitFullscreen() { + var fsApi = _fullscreenApi2['default']; + + this.isFullscreen(false); + + // Check for browser element fullscreen support + if (fsApi.requestFullscreen) { + _document2['default'][fsApi.exitFullscreen](); + } else if (this.tech_.supportsFullScreen()) { + this.techCall_('exitFullScreen'); + } else { + this.exitFullWindow(); + /** + * @event Player#fullscreenchange + * @type {EventTarget~Event} + */ + this.trigger('fullscreenchange'); + } + }; + + /** + * When fullscreen isn't supported we can stretch the + * video container to as wide as the browser will let us. + * + * @fires Player#enterFullWindow + */ + + + Player.prototype.enterFullWindow = function enterFullWindow() { + this.isFullWindow = true; + + // Storing original doc overflow value to return to when fullscreen is off + this.docOrigOverflow = _document2['default'].documentElement.style.overflow; + + // Add listener for esc key to exit fullscreen + Events.on(_document2['default'], 'keydown', Fn.bind(this, this.fullWindowOnEscKey)); + + // Hide any scroll bars + _document2['default'].documentElement.style.overflow = 'hidden'; + + // Apply fullscreen styles + Dom.addClass(_document2['default'].body, 'vjs-full-window'); + + /** + * @event Player#enterFullWindow + * @type {EventTarget~Event} + */ + 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 + */ + + + Player.prototype.fullWindowOnEscKey = function fullWindowOnEscKey(event) { + if (event.keyCode === 27) { + if (this.isFullscreen() === true) { + this.exitFullscreen(); + } else { + this.exitFullWindow(); + } + } + }; + + /** + * Exit full window + * + * @fires Player#exitFullWindow + */ + + + Player.prototype.exitFullWindow = function exitFullWindow() { + this.isFullWindow = false; + Events.off(_document2['default'], 'keydown', this.fullWindowOnEscKey); + + // Unhide scroll bars. + _document2['default'].documentElement.style.overflow = this.docOrigOverflow; + + // Remove fullscreen styles + Dom.removeClass(_document2['default'].body, 'vjs-full-window'); + + // Resize the box, controller, and poster to original sizes + // this.positionAll(); + /** + * @event Player#exitFullWindow + * @type {EventTarget~Event} + */ + this.trigger('exitFullWindow'); + }; + + /** + * Check whether the player can play a given mimetype + * + * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype + * + * @param {string} type + * The mimetype to check + * + * @return {string} + * 'probably', 'maybe', or '' (empty string) + */ + + + Player.prototype.canPlayType = function canPlayType(type) { + var can = void 0; + + // Loop through each playback technology in the options order + for (var i = 0, j = this.options_.techOrder; i < j.length; i++) { + var techName = j[i]; + var tech = _tech2['default'].getTech(techName); + + // Support old behavior of techs being registered as components. + // Remove once that deprecated behavior is removed. + if (!tech) { + tech = _component2['default'].getComponent(techName); + } + + // Check if the current tech is defined before continuing + if (!tech) { + _log2['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 or false + */ + + + Player.prototype.selectSource = function selectSource(sources) { + var _this5 = this; + + // Get only the techs specified in `techOrder` that exist and are supported by the + // current platform + var techs = this.options_.techOrder.map(function (techName) { + return [techName, _tech2['default'].getTech(techName)]; + }).filter(function (_ref) { + var techName = _ref[0], + tech = _ref[1]; + + // Check if the current tech is defined before continuing + if (tech) { + // Check if the browser supports this technology + return tech.isSupported(); + } + + _log2['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 = void 0; + + outerArray.some(function (outerChoice) { + return innerArray.some(function (innerChoice) { + found = tester(outerChoice, innerChoice); + + if (found) { + return true; + } + }); + }); + + return found; + }; + + var foundSourceAndTech = void 0; + var flip = function flip(fn) { + return function (a, b) { + return fn(b, a); + }; + }; + var finder = function finder(_ref2, source) { + var techName = _ref2[0], + tech = _ref2[1]; + + if (tech.canPlaySource(source, _this5.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. + * + * @param {Tech~SourceObject|Tech~SourceObject[]} [source] + * One SourceObject or an array of SourceObjects + * + * @return {string} + * The current video source when getting + */ + + + Player.prototype.src = function src(source) { + var _this6 = this; + + // getter usage + if (typeof source === 'undefined') { + return this.cache_.src; + } + // filter out invalid sources and turn our source into + // an array of source objects + var sources = (0, _filterSource2['default'])(source); + + // if a source was passed in then it is invalid because + // it was filtered to a zero length Array. So we have to + // show an error + if (!sources.length) { + this.setTimeout(function () { + this.error({ code: 4, message: this.localize(this.options_.notSupportedMessage) }); + }, 0); + return; + } + + // intial sources + this.cache_.sources = sources; + this.changingSrc_ = true; + + // intial source + this.cache_.source = sources[0]; + + // middlewareSource is the source after it has been changed by middleware + middleware.setSource(this, sources[0], function (middlewareSource, mws) { + _this6.middleware_ = mws; + + var err = _this6.src_(middlewareSource); + + if (err) { + if (sources.length > 1) { + return _this6.src(sources.slice(1)); + } + + // We need to wrap this in a timeout to give folks a chance to add error event handlers + _this6.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 + _this6.triggerReady(); + + return; + } + + _this6.changingSrc_ = false; + // video element listed source + _this6.cache_.src = middlewareSource.src; + + middleware.setTech(mws, _this6.tech_); + }); + }; + + /** + * Set the source object on the tech, returns a boolean that indicates wether + * there is a tech that can play the source or not + * + * @param {Tech~SourceObject} source + * The source object to set on the Tech + * + * @return {Boolean} + * - True if there is no Tech to playback this source + * - False otherwise + * + * @private + */ + + + Player.prototype.src_ = function src_(source) { + var sourceTech = this.selectSource([source]); + + if (!sourceTech) { + return true; + } + + if (!(0, _toTitleCase.titleCaseEquals)(sourceTech.tech, this.techName_)) { + this.changingSrc_ = true; + + // load this technology with the chosen source + this.loadTech_(sourceTech.tech, sourceTech.source); + return false; + } + + // 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 (this.tech_.constructor.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 false; + }; + + /** + * Begin loading the src data. + */ + + + Player.prototype.load = function load() { + this.techCall_('load'); + }; + + /** + * Reset the player. Loads the first tech in the techOrder, + * and calls `reset` on the tech`. + */ + + + Player.prototype.reset = function reset() { + this.loadTech_(this.options_.techOrder[0], null); + this.techCall_('reset'); + }; + + /** + * Returns all of the current source objects. + * + * @return {Tech~SourceObject[]} + * The current source objects + */ + + + Player.prototype.currentSources = function currentSources() { + var source = this.currentSource(); + var sources = []; + + // assume `{}` or `{ src }` + if (Object.keys(source).length !== 0) { + sources.push(source); + } + + return this.cache_.sources || sources; + }; + + /** + * Returns the current source object. + * + * @return {Tech~SourceObject} + * The current source object + */ + + + Player.prototype.currentSource = function currentSource() { + return this.cache_.source || {}; + }; + + /** + * 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 + */ + + + Player.prototype.currentSrc = function currentSrc() { + return this.currentSource() && this.currentSource().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 + */ + + + Player.prototype.currentType = function currentType() { + return this.currentSource() && this.currentSource().type || ''; + }; + + /** + * Get or set the preload attribute + * + * @param {boolean} [value] + * - true means that we should preload + * - false maens that we should not preload + * + * @return {string} + * The preload attribute value when getting + */ + + + Player.prototype.preload = function preload(value) { + if (value !== undefined) { + this.techCall_('setPreload', value); + this.options_.preload = value; + return; + } + return this.techGet_('preload'); + }; + + /** + * Get or set the autoplay attribute. + * + * @param {boolean} [value] + * - true means that we should autoplay + * - false means that we should not autoplay + * + * @return {string} + * The current value of autoplay when getting + */ + + + Player.prototype.autoplay = function autoplay(value) { + if (value !== undefined) { + this.techCall_('setAutoplay', value); + this.options_.autoplay = value; + return; + } + return this.techGet_('autoplay', value); + }; + + /** + * Set or unset the playsinline attribute. + * Playsinline tells the browser that non-fullscreen playback is preferred. + * + * @param {boolean} [value] + * - true means that we should try to play inline by default + * - false means that we should use the browser's default playback mode, + * which in most cases is inline. iOS Safari is a notable exception + * and plays fullscreen by default. + * + * @return {string|Player} + * - the current value of playsinline + * - the player when setting + * + * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} + */ + + + Player.prototype.playsinline = function playsinline(value) { + if (value !== undefined) { + this.techCall_('setPlaysinline', value); + this.options_.playsinline = value; + return this; + } + return this.techGet_('playsinline'); + }; + + /** + * Get or set the loop attribute on the video element. + * + * @param {boolean} [value] + * - true means that we should loop the video + * - false means that we should not loop the video + * + * @return {string} + * The current value of loop when getting + */ + + + Player.prototype.loop = function loop(value) { + if (value !== undefined) { + this.techCall_('setLoop', value); + this.options_.loop = value; + return; + } + return this.techGet_('loop'); + }; + + /** + * Get or set the poster image source url + * + * @fires Player#posterchange + * + * @param {string} [src] + * Poster image source URL + * + * @return {string} + * The current value of poster when getting + */ + + + 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 event fires when the poster image is changed on the player. + * + * @event Player#posterchange + * @type {EventTarget~Event} + */ + this.trigger('posterchange'); + }; + + /** + * 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 + * source if the player user hasn't set a poster through + * the normal APIs. + * + * @fires Player#posterchange + * @listens Tech#posterchange + * @private + */ + + + 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. + * + * @fires Player#controlsenabled + * + * @param {boolean} [bool] + * - true to turn controls on + * - false to turn controls off + * + * @return {boolean} + * The current value of controls when getting + */ + + + Player.prototype.controls = function controls(bool) { + if (bool !== undefined) { + bool = !!bool; + + // 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'); + /** + * @event Player#controlsenabled + * @type {EventTarget~Event} + */ + this.trigger('controlsenabled'); + + if (!this.usingNativeControls()) { + this.addTechControlsListeners_(); + } + } else { + this.removeClass('vjs-controls-enabled'); + this.addClass('vjs-controls-disabled'); + /** + * @event Player#controlsdisabled + * @type {EventTarget~Event} + */ + this.trigger('controlsdisabled'); + + if (!this.usingNativeControls()) { + this.removeTechControlsListeners_(); + } + } + } + return; + } + 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** + * + * @fires Player#usingnativecontrols + * @fires Player#usingcustomcontrols + * + * @param {boolean} [bool] + * - true to turn native controls on + * - false to turn native controls off + * + * @return {boolean} + * The current value of native controls when getting + */ + + + Player.prototype.usingNativeControls = function usingNativeControls(bool) { + if (bool !== undefined) { + bool = !!bool; + + // 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 Player#usingnativecontrols + * @type {EventTarget~Event} + */ + this.trigger('usingnativecontrols'); + } else { + this.removeClass('vjs-using-native-controls'); + + /** + * player is using the custom HTML controls + * + * @event Player#usingcustomcontrols + * @type {EventTarget~Event} + */ + this.trigger('usingcustomcontrols'); + } + } + return; + } + return !!this.usingNativeControls_; + }; + + /** + * Set or get the current MediaError + * + * @fires Player#error + * + * @param {MediaError|string|number} [err] + * A MediaError or a string/number to be turned + * into a MediaError + * + * @return {MediaError|null} + * The current MediaError when getting (or null) + */ + + + 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'); + if (this.errorDisplay) { + this.errorDisplay.close(); + } + return; + } + + this.error_ = new _mediaError2['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 + _log2['default'].error('(CODE:' + this.error_.code + ' ' + _mediaError2['default'].errorTypes[this.error_.code] + ')', this.error_.message, this.error_); + + /** + * @event Player#error + * @type {EventTarget~Event} + */ + this.trigger('error'); + + return; + }; + + /** + * Report user activity + * + * @param {Object} event + * Event object + */ + + + Player.prototype.reportUserActivity = function reportUserActivity(event) { + this.userActivity_ = true; + }; + + /** + * Get/set if user is active + * + * @fires Player#useractive + * @fires Player#userinactive + * + * @param {boolean} [bool] + * - true if the user is active + * - false if the user is inactive + * + * @return {boolean} + * The current value of userActive when getting + */ + + + 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'); + /** + * @event Player#useractive + * @type {EventTarget~Event} + */ + 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'); + /** + * @event Player#userinactive + * @type {EventTarget~Event} + */ + this.trigger('userinactive'); + } + } + return; + } + return this.userActive_; + }; + + /** + * Listen for user activity based on timeout value + * + * @private + */ + + + Player.prototype.listenForUserActivity_ = function listenForUserActivity_() { + var mouseInProgress = void 0; + var lastMoveX = void 0; + var lastMoveY = void 0; + 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 = void 0; + + 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 activity check 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} + * The current playback rate when getting or 1.0 + */ + + + Player.prototype.playbackRate = function playbackRate(rate) { + if (rate !== undefined) { + this.techCall_('setPlaybackRate', rate); + return; + } + + if (this.tech_ && this.tech_.featuresPlaybackRate) { + return this.techGet_('playbackRate'); + } + return 1.0; + }; + + /** + * Gets or sets the current default playback rate. A default playback rate of + * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance. + * defaultPlaybackRate will only represent what the intial playbackRate of a video was, not + * not the current playbackRate. + * + * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate + * + * @param {number} [rate] + * New default playback rate to set. + * + * @return {number|Player} + * - The default playback rate when getting or 1.0 + * - the player when setting + */ + + + Player.prototype.defaultPlaybackRate = function defaultPlaybackRate(rate) { + if (rate !== undefined) { + return this.techCall_('setDefaultPlaybackRate', rate); + } + + if (this.tech_ && this.tech_.featuresPlaybackRate) { + return this.techGet_('defaultPlaybackRate'); + } + return 1.0; + }; + + /** + * Gets or sets the audio flag + * + * @param {boolean} bool + * - true signals that this is an audio player + * - false signals that this is not an audio player + * + * @return {boolean} + * The current value of isAudio when getting + */ + + + Player.prototype.isAudio = function isAudio(bool) { + if (bool !== undefined) { + this.isAudio_ = !!bool; + return; + } + + return !!this.isAudio_; + }; + + /** + * A helper method for adding a {@link TextTrack} to our + * {@link TextTrackList}. + * + * In addition to the W3C settings we allow adding additional info through options. + * + * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack + * + * @param {string} [kind] + * the kind of TextTrack you are adding + * + * @param {string} [label] + * the label to give the TextTrack label + * + * @param {string} [language] + * the language to set on the TextTrack + * + * @return {TextTrack|undefined} + * the TextTrack that was added or undefined + * if there is no tech + */ + + + Player.prototype.addTextTrack = function addTextTrack(kind, label, language) { + if (this.tech_) { + return this.tech_.addTextTrack(kind, label, language); + } + }; + + /** + * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}. It will + * automatically removed from the video element whenever the source changes, unless + * manualCleanup is set to false. + * + * @param {Object} options + * Options to pass to {@link HTMLTrackElement} during creation. See + * {@link HTMLTrackElement} for object properties that you should use. + * + * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be + * + * @return {HtmlTrackElement} + * the HTMLTrackElement that was created and added + * to the HtmlTrackElementList and the remote + * TextTrackList + * + * @deprecated The default value of the "manualCleanup" parameter will default + * to "false" in upcoming versions of Video.js + */ + + + Player.prototype.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { + if (this.tech_) { + return this.tech_.addRemoteTextTrack(options, manualCleanup); + } + }; + + /** + * Remove a remote {@link TextTrack} from the respective + * {@link TextTrackList} and {@link HtmlTrackElementList}. + * + * @param {Object} track + * Remote {@link TextTrack} to remove + * + * @return {undefined} + * does not return anything + */ + + + Player.prototype.removeRemoteTextTrack = function removeRemoteTextTrack() { + var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, + _ref3$track = _ref3.track, + track = _ref3$track === undefined ? arguments[0] : _ref3$track; + + // 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 + + if (this.tech_) { + return this.tech_.removeRemoteTextTrack(track); + } + }; + + /** + * Gets available media playback quality metrics as specified by the W3C's Media + * Playback Quality API. + * + * @see [Spec]{@link https://wicg.github.io/media-playback-quality} + * + * @return {Object|undefined} + * An object with supported media playback quality metrics or undefined if there + * is no tech or the tech does not support it. + */ + + + Player.prototype.getVideoPlaybackQuality = function getVideoPlaybackQuality() { + return this.techGet_('getVideoPlaybackQuality'); + }; + + /** + * Get video width + * + * @return {number} + * current video width + */ + + + Player.prototype.videoWidth = function videoWidth() { + return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0; + }; + + /** + * Get video height + * + * @return {number} + * current video height + */ + + + Player.prototype.videoHeight = function videoHeight() { + return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0; + }; + + /** + * 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 language code to set the player to + * + * @return {string} + * The current language code when getting + */ + + + Player.prototype.language = function language(code) { + if (code === undefined) { + return this.language_; + } + + this.language_ = String(code).toLowerCase(); + }; + + /** + * 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} + * An array of of supported languages + */ + + + Player.prototype.languages = function languages() { + return (0, _mergeOptions2['default'])(Player.prototype.options_.languages, this.languages_); + }; + + /** + * returns a JavaScript object reperesenting the current track + * information. **DOES not return it as JSON** + * + * @return {Object} + * Object representing the current of track info + */ + + + Player.prototype.toJSON = function toJSON() { + var options = (0, _mergeOptions2['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 = (0, _mergeOptions2['default'])(track); + track.player = undefined; + options.tracks[i] = track; + } + + return options; + }; + + /** + * Creates a simple modal dialog (an instance of the {@link ModalDialog} + * component) that immediately overlays the player with arbitrary + * content and removes itself when closed. + * + * @param {string|Function|Element|Array|null} content + * Same as {@link 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 {@link ModalDialog}. + * + * @return {ModalDialog} + * the {@link ModalDialog} that was created + */ + + + Player.prototype.createModal = function createModal(content, options) { + var _this7 = this; + + options = options || {}; + options.content = content || ''; + + var modal = new _modalDialog2['default'](this, options); + + this.addChild(modal); + modal.on('dispose', function () { + _this7.removeChild(modal); + }); + + modal.open(); + return modal; + }; + + /** + * Gets tag settings + * + * @param {Element} tag + * The player tag + * + * @return {Object} + * An object containing all of the settings + * for a player tag + */ + + + Player.getTagSettings = function getTagSettings(tag) { + var baseOptions = { + sources: [], + tracks: [] + }; + + var tagOptions = Dom.getAttributes(tag); + var dataSetup = tagOptions['data-setup']; + + if (Dom.hasClass(tag, 'vjs-fluid')) { + tagOptions.fluid = true; + } + + // Check if data-setup attr exists. + if (dataSetup !== null) { + // Parse options JSON + // If empty string, make it a parsable json object. + var _safeParseTuple = (0, _tuple2['default'])(dataSetup || '{}'), + err = _safeParseTuple[0], + data = _safeParseTuple[1]; + + if (err) { + _log2['default'].error(err); + } + (0, _obj.assign)(tagOptions, data); + } + + (0, _obj.assign)(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.getAttributes(child)); + } else if (childName === 'track') { + baseOptions.tracks.push(Dom.getAttributes(child)); + } + } + } + + return baseOptions; + }; + + /** + * Determine wether or not flexbox is supported + * + * @return {boolean} + * - true if flexbox is supported + * - false if flexbox is not supported + */ + + + Player.prototype.flexNotSupported_ = function flexNotSupported_() { + var elem = _document2['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 || + // IE10-specific (2012 flex spec) + 'msFlexOrder' in elem.style); + }; + + return Player; +}(_component2['default']); + +/** + * Get the {@link VideoTrackList} + * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist + * + * @return {VideoTrackList} + * the current video track list + * + * @method Player.prototype.videoTracks + */ + +/** + * Get the {@link AudioTrackList} + * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist + * + * @return {AudioTrackList} + * the current audio track list + * + * @method Player.prototype.audioTracks + */ + +/** + * Get the {@link TextTrackList} + * + * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks + * + * @return {TextTrackList} + * the current text track list + * + * @method Player.prototype.textTracks + */ + +/** + * Get the remote {@link TextTrackList} + * + * @return {TextTrackList} + * The current remote text track list + * + * @method Player.prototype.textTracks + */ + +/** + * Get the remote {@link HtmlTrackElementList} tracks. + * + * @return {HtmlTrackElementList} + * The current remote text track element list + * + * @method Player.prototype.remoteTextTrackEls + */ + +_trackTypes.ALL.names.forEach(function (name) { + var props = _trackTypes.ALL[name]; + + Player.prototype[props.getterName] = function () { + if (this.tech_) { + return this.tech_[props.getterName](); + } + + // if we have not yet loadTech_, we create {video,audio,text}Tracks_ + // these will be passed to the tech during loading + this[props.privateName] = this[props.privateName] || new props.ListClass(); + return this[props.privateName]; + }; +}); + +/** + * Global player list + * + * @type {Object} + */ +Player.players = {}; + +var navigator = _window2['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: _tech2['default'].defaultTechOrder_, + + html5: {}, + flash: {}, + + // 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: navigator && (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.' +}; + +[ +/** + * 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 Player#ended + */ +'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 Player#seeking + */ +'seeking', +/** + * Returns the TimeRanges of the media that are currently available + * for seeking to. + * + * @return {TimeRanges} the seekable intervals of the media timeline + * @method Player#seekable + */ +'seekable', +/** + * 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 Player#networkState + */ +'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 Player#readyState + */ +'readyState'].forEach(function (fn) { + Player.prototype[fn] = function () { + return this.techGet_(fn); + }; +}); + +TECH_EVENTS_RETRIGGER.forEach(function (event) { + Player.prototype['handleTech' + (0, _toTitleCase2['default'])(event) + '_'] = function () { + return this.trigger(event); + }; +}); + +/** + * Fired when the player has initial duration and dimension information + * + * @event Player#loadedmetadata + * @type {EventTarget~Event} + */ + +/** + * Fired when the player has downloaded data at the current playback position + * + * @event Player#loadeddata + * @type {EventTarget~Event} + */ + +/** + * 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 Player#timeupdate + * @type {EventTarget~Event} + */ + +/** + * Fired when the volume changes + * + * @event Player#volumechange + * @type {EventTarget~Event} + */ + +/** + * Reports whether or not a player has a plugin available. + * + * This does not report whether or not the plugin has ever been initialized + * on this player. For that, [usingPlugin]{@link Player#usingPlugin}. + * + * @method Player#hasPlugin + * @param {string} name + * The name of a plugin. + * + * @return {boolean} + * Whether or not this player has the requested plugin available. + */ + +/** + * Reports whether or not a player is using a plugin by name. + * + * For basic plugins, this only reports whether the plugin has _ever_ been + * initialized on this player. + * + * @method Player#usingPlugin + * @param {string} name + * The name of a plugin. + * + * @return {boolean} + * Whether or not this player is using the requested plugin. + */ + +_component2['default'].registerComponent('Player', Player); +exports['default'] = Player; + +},{"1":1,"100":100,"102":102,"103":103,"4":4,"44":44,"47":47,"48":48,"49":49,"5":5,"53":53,"55":55,"58":58,"61":61,"62":62,"63":63,"64":64,"70":70,"71":71,"73":73,"77":77,"8":8,"81":81,"82":82,"85":85,"86":86,"87":87,"88":88,"90":90,"91":91,"92":92,"93":93,"94":94,"95":95,"96":96,"99":99}],57:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + +var _evented = _dereq_(53); + +var _evented2 = _interopRequireDefault(_evented); + +var _stateful = _dereq_(54); + +var _stateful2 = _interopRequireDefault(_stateful); + +var _events = _dereq_(86); + +var Events = _interopRequireWildcard(_events); + +var _fn = _dereq_(88); + +var Fn = _interopRequireWildcard(_fn); + +var _log = _dereq_(91); + +var _log2 = _interopRequireDefault(_log); + +var _player = _dereq_(56); + +var _player2 = _interopRequireDefault(_player); + +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"); } } /** + * @file plugin.js + */ + + +/** + * The base plugin name. + * + * @private + * @constant + * @type {string} + */ +var BASE_PLUGIN_NAME = 'plugin'; + +/** + * The key on which a player's active plugins cache is stored. + * + * @private + * @constant + * @type {string} + */ +var PLUGIN_CACHE_KEY = 'activePlugins_'; + +/** + * Stores registered plugins in a private space. + * + * @private + * @type {Object} + */ +var pluginStorage = {}; + +/** + * Reports whether or not a plugin has been registered. + * + * @private + * @param {string} name + * The name of a plugin. + * + * @returns {boolean} + * Whether or not the plugin has been registered. + */ +var pluginExists = function pluginExists(name) { + return pluginStorage.hasOwnProperty(name); +}; + +/** + * Get a single registered plugin by name. + * + * @private + * @param {string} name + * The name of a plugin. + * + * @returns {Function|undefined} + * The plugin (or undefined). + */ +var getPlugin = function getPlugin(name) { + return pluginExists(name) ? pluginStorage[name] : undefined; +}; + +/** + * Marks a plugin as "active" on a player. + * + * Also, ensures that the player has an object for tracking active plugins. + * + * @private + * @param {Player} player + * A Video.js player instance. + * + * @param {string} name + * The name of a plugin. + */ +var markPluginAsActive = function markPluginAsActive(player, name) { + player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {}; + player[PLUGIN_CACHE_KEY][name] = true; +}; + +/** + * Triggers a pair of plugin setup events. + * + * @private + * @param {Player} player + * A Video.js player instance. + * + * @param {Plugin~PluginEventHash} hash + * A plugin event hash. + * + * @param {Boolean} [before] + * If true, prefixes the event name with "before". In other words, + * use this to trigger "beforepluginsetup" instead of "pluginsetup". + */ +var triggerSetupEvent = function triggerSetupEvent(player, hash, before) { + var eventName = (before ? 'before' : '') + 'pluginsetup'; + + player.trigger(eventName, hash); + player.trigger(eventName + ':' + hash.name, hash); +}; + +/** + * Takes a basic plugin function and returns a wrapper function which marks + * on the player that the plugin has been activated. + * + * @private + * @param {string} name + * The name of the plugin. + * + * @param {Function} plugin + * The basic plugin. + * + * @returns {Function} + * A wrapper function for the given plugin. + */ +var createBasicPlugin = function createBasicPlugin(name, plugin) { + var basicPluginWrapper = function basicPluginWrapper() { + + // We trigger the "beforepluginsetup" and "pluginsetup" events on the player + // regardless, but we want the hash to be consistent with the hash provided + // for advanced plugins. + // + // The only potentially counter-intuitive thing here is the `instance` in + // the "pluginsetup" event is the value returned by the `plugin` function. + triggerSetupEvent(this, { name: name, plugin: plugin, instance: null }, true); + + var instance = plugin.apply(this, arguments); + + markPluginAsActive(this, name); + triggerSetupEvent(this, { name: name, plugin: plugin, instance: instance }); + + return instance; + }; + + Object.keys(plugin).forEach(function (prop) { + basicPluginWrapper[prop] = plugin[prop]; + }); + + return basicPluginWrapper; +}; + +/** + * Takes a plugin sub-class and returns a factory function for generating + * instances of it. + * + * This factory function will replace itself with an instance of the requested + * sub-class of Plugin. + * + * @private + * @param {string} name + * The name of the plugin. + * + * @param {Plugin} PluginSubClass + * The advanced plugin. + * + * @returns {Function} + */ +var createPluginFactory = function createPluginFactory(name, PluginSubClass) { + + // Add a `name` property to the plugin prototype so that each plugin can + // refer to itself by name. + PluginSubClass.prototype.name = name; + + return function () { + triggerSetupEvent(this, { name: name, plugin: PluginSubClass, instance: null }, true); + + for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + var instance = new (Function.prototype.bind.apply(PluginSubClass, [null].concat([this].concat(args))))(); + + // The plugin is replaced by a function that returns the current instance. + this[name] = function () { + return instance; + }; + + triggerSetupEvent(this, instance.getEventHash()); + + return instance; + }; +}; + +/** + * Parent class for all advanced plugins. + * + * @mixes module:evented~EventedMixin + * @mixes module:stateful~StatefulMixin + * @fires Player#beforepluginsetup + * @fires Player#beforepluginsetup:$name + * @fires Player#pluginsetup + * @fires Player#pluginsetup:$name + * @listens Player#dispose + * @throws {Error} + * If attempting to instantiate the base {@link Plugin} class + * directly instead of via a sub-class. + */ + +var Plugin = function () { + + /** + * Creates an instance of this class. + * + * Sub-classes should call `super` to ensure plugins are properly initialized. + * + * @param {Player} player + * A Video.js player instance. + */ + function Plugin(player) { + _classCallCheck(this, Plugin); + + if (this.constructor === Plugin) { + throw new Error('Plugin must be sub-classed; not directly instantiated.'); + } + + this.player = player; + + // Make this object evented, but remove the added `trigger` method so we + // use the prototype version instead. + (0, _evented2['default'])(this); + delete this.trigger; + + (0, _stateful2['default'])(this, this.constructor.defaultState); + markPluginAsActive(player, this.name); + + // Auto-bind the dispose method so we can use it as a listener and unbind + // it later easily. + this.dispose = Fn.bind(this, this.dispose); + + // If the player is disposed, dispose the plugin. + player.on('dispose', this.dispose); + } + + /** + * Each event triggered by plugins includes a hash of additional data with + * conventional properties. + * + * This returns that object or mutates an existing hash. + * + * @param {Object} [hash={}] + * An object to be used as event an event hash. + * + * @returns {Plugin~PluginEventHash} + * An event hash object with provided properties mixed-in. + */ + + + Plugin.prototype.getEventHash = function getEventHash() { + var hash = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + + hash.name = this.name; + hash.plugin = this.constructor; + hash.instance = this; + return hash; + }; + + /** + * Triggers an event on the plugin object and overrides + * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}. + * + * @param {string|Object} event + * An event type or an object with a type property. + * + * @param {Object} [hash={}] + * Additional data hash to merge with a + * {@link Plugin~PluginEventHash|PluginEventHash}. + * + * @returns {boolean} + * Whether or not default was prevented. + */ + + + Plugin.prototype.trigger = function trigger(event) { + var hash = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + return Events.trigger(this.eventBusEl_, event, this.getEventHash(hash)); + }; + + /** + * Handles "statechanged" events on the plugin. No-op by default, override by + * subclassing. + * + * @abstract + * @param {Event} e + * An event object provided by a "statechanged" event. + * + * @param {Object} e.changes + * An object describing changes that occurred with the "statechanged" + * event. + */ + + + Plugin.prototype.handleStateChanged = function handleStateChanged(e) {}; + + /** + * Disposes a plugin. + * + * Subclasses can override this if they want, but for the sake of safety, + * it's probably best to subscribe the "dispose" event. + * + * @fires Plugin#dispose + */ + + + Plugin.prototype.dispose = function dispose() { + var name = this.name, + player = this.player; + + /** + * Signals that a advanced plugin is about to be disposed. + * + * @event Plugin#dispose + * @type {EventTarget~Event} + */ + + this.trigger('dispose'); + this.off(); + player.off('dispose', this.dispose); + + // Eliminate any possible sources of leaking memory by clearing up + // references between the player and the plugin instance and nulling out + // the plugin's state and replacing methods with a function that throws. + player[PLUGIN_CACHE_KEY][name] = false; + this.player = this.state = null; + + // Finally, replace the plugin name on the player with a new factory + // function, so that the plugin is ready to be set up again. + player[name] = createPluginFactory(name, pluginStorage[name]); + }; + + /** + * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`). + * + * @param {string|Function} plugin + * If a string, matches the name of a plugin. If a function, will be + * tested directly. + * + * @returns {boolean} + * Whether or not a plugin is a basic plugin. + */ + + + Plugin.isBasic = function isBasic(plugin) { + var p = typeof plugin === 'string' ? getPlugin(plugin) : plugin; + + return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype); + }; + + /** + * Register a Video.js plugin. + * + * @param {string} name + * The name of the plugin to be registered. Must be a string and + * must not match an existing plugin or a method on the `Player` + * prototype. + * + * @param {Function} plugin + * A sub-class of `Plugin` or a function for basic plugins. + * + * @returns {Function} + * For advanced plugins, a factory function for that plugin. For + * basic plugins, a wrapper function that initializes the plugin. + */ + + + Plugin.registerPlugin = function registerPlugin(name, plugin) { + if (typeof name !== 'string') { + throw new Error('Illegal plugin name, "' + name + '", must be a string, was ' + (typeof name === 'undefined' ? 'undefined' : _typeof(name)) + '.'); + } + + if (pluginExists(name)) { + _log2['default'].warn('A plugin named "' + name + '" already exists. You may want to avoid re-registering plugins!'); + } else if (_player2['default'].prototype.hasOwnProperty(name)) { + throw new Error('Illegal plugin name, "' + name + '", cannot share a name with an existing player method!'); + } + + if (typeof plugin !== 'function') { + throw new Error('Illegal plugin for "' + name + '", must be a function, was ' + (typeof plugin === 'undefined' ? 'undefined' : _typeof(plugin)) + '.'); + } + + pluginStorage[name] = plugin; + + // Add a player prototype method for all sub-classed plugins (but not for + // the base Plugin class). + if (name !== BASE_PLUGIN_NAME) { + if (Plugin.isBasic(plugin)) { + _player2['default'].prototype[name] = createBasicPlugin(name, plugin); + } else { + _player2['default'].prototype[name] = createPluginFactory(name, plugin); + } + } + + return plugin; + }; + + /** + * De-register a Video.js plugin. + * + * @param {string} name + * The name of the plugin to be deregistered. + */ + + + Plugin.deregisterPlugin = function deregisterPlugin(name) { + if (name === BASE_PLUGIN_NAME) { + throw new Error('Cannot de-register base plugin.'); + } + if (pluginExists(name)) { + delete pluginStorage[name]; + delete _player2['default'].prototype[name]; + } + }; + + /** + * Gets an object containing multiple Video.js plugins. + * + * @param {Array} [names] + * If provided, should be an array of plugin names. Defaults to _all_ + * plugin names. + * + * @returns {Object|undefined} + * An object containing plugin(s) associated with their name(s) or + * `undefined` if no matching plugins exist). + */ + + + Plugin.getPlugins = function getPlugins() { + var names = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Object.keys(pluginStorage); + + var result = void 0; + + names.forEach(function (name) { + var plugin = getPlugin(name); + + if (plugin) { + result = result || {}; + result[name] = plugin; + } + }); + + return result; + }; + + /** + * Gets a plugin's version, if available + * + * @param {string} name + * The name of a plugin. + * + * @returns {string} + * The plugin's version or an empty string. + */ + + + Plugin.getPluginVersion = function getPluginVersion(name) { + var plugin = getPlugin(name); + + return plugin && plugin.VERSION || ''; + }; + + return Plugin; +}(); + +/** + * Gets a plugin by name if it exists. + * + * @static + * @method getPlugin + * @memberOf Plugin + * @param {string} name + * The name of a plugin. + * + * @returns {Function|undefined} + * The plugin (or `undefined`). + */ + + +Plugin.getPlugin = getPlugin; + +/** + * The name of the base plugin class as it is registered. + * + * @type {string} + */ +Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME; + +Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin); + +/** + * Documented in player.js + * + * @ignore + */ +_player2['default'].prototype.usingPlugin = function (name) { + return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true; +}; + +/** + * Documented in player.js + * + * @ignore + */ +_player2['default'].prototype.hasPlugin = function (name) { + return !!pluginExists(name); +}; + +exports['default'] = Plugin; + +/** + * Signals that a plugin is about to be set up on a player. + * + * @event Player#beforepluginsetup + * @type {Plugin~PluginEventHash} + */ + +/** + * Signals that a plugin is about to be set up on a player - by name. The name + * is the name of the plugin. + * + * @event Player#beforepluginsetup:$name + * @type {Plugin~PluginEventHash} + */ + +/** + * Signals that a plugin has just been set up on a player. + * + * @event Player#pluginsetup + * @type {Plugin~PluginEventHash} + */ + +/** + * Signals that a plugin has just been set up on a player - by name. The name + * is the name of the plugin. + * + * @event Player#pluginsetup:$name + * @type {Plugin~PluginEventHash} + */ + +/** + * @typedef {Object} Plugin~PluginEventHash + * + * @property {string} instance + * For basic plugins, the return value of the plugin function. For + * advanced plugins, the plugin instance on which the event is fired. + * + * @property {string} name + * The name of the plugin. + * + * @property {string} plugin + * For basic plugins, the plugin function. For advanced plugins, the + * plugin class/constructor. + */ + +},{"53":53,"54":54,"56":56,"86":86,"88":88,"91":91}],58:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _clickableComponent = _dereq_(3); + +var _clickableComponent2 = _interopRequireDefault(_clickableComponent); + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _fn = _dereq_(88); + +var Fn = _interopRequireWildcard(_fn); + +var _dom = _dereq_(85); + +var Dom = _interopRequireWildcard(_dom); + +var _browser = _dereq_(81); + +var browser = _interopRequireWildcard(_browser); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file poster-image.js + */ + + +/** + * A `ClickableComponent` that handles showing the poster image for the player. + * + * @extends ClickableComponent + */ +var PosterImage = function (_ClickableComponent) { + _inherits(PosterImage, _ClickableComponent); + + /** + * Create an instance of this class. + * + * @param {Player} player + * The `Player` that this class should attach to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function PosterImage(player, options) { + _classCallCheck(this, PosterImage); + + var _this = _possibleConstructorReturn(this, _ClickableComponent.call(this, player, options)); + + _this.update(); + player.on('posterchange', Fn.bind(_this, _this.update)); + return _this; + } + + /** + * Clean up and dispose of the `PosterImage`. + */ + + + PosterImage.prototype.dispose = function dispose() { + this.player().off('posterchange', this.update); + _ClickableComponent.prototype.dispose.call(this); + }; + + /** + * Create the `PosterImage`s DOM element. + * + * @return {Element} + * The element that gets created. + */ + + + 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; + }; + + /** + * An {@link EventTarget~EventListener} for {@link Player#posterchange} events. + * + * @listens Player#posterchange + * + * @param {EventTarget~Event} [event] + * The `Player#posterchange` event that triggered this function. + */ + + + PosterImage.prototype.update = function update(event) { + 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 source of the `PosterImage` depending on the display method. + * + * @param {string} url + * The URL to the source for the `PosterImage`. + */ + + + 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; + } + }; + + /** + * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See + * {@link ClickableComponent#handleClick} for instances where this will be triggered. + * + * @listens tap + * @listens click + * @listens keydown + * + * @param {EventTarget~Event} event + + The `click`, `tap` or `keydown` event that caused this function to be called. + */ + + + PosterImage.prototype.handleClick = function handleClick(event) { + // We don't want a click to trigger playback when controls are disabled + if (!this.player_.controls()) { + return; + } + + if (this.player_.paused()) { + this.player_.play(); + } else { + this.player_.pause(); + } + }; + + return PosterImage; +}(_clickableComponent2['default']); + +_component2['default'].registerComponent('PosterImage', PosterImage); +exports['default'] = PosterImage; + +},{"3":3,"5":5,"81":81,"85":85,"88":88}],59:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; +exports.hasLoaded = exports.autoSetupTimeout = exports.autoSetup = undefined; + +var _dom = _dereq_(85); + +var Dom = _interopRequireWildcard(_dom); + +var _events = _dereq_(86); + +var Events = _interopRequireWildcard(_events); + +var _document = _dereq_(99); + +var _document2 = _interopRequireDefault(_document); + +var _window = _dereq_(100); + +var _window2 = _interopRequireDefault(_window); + +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; } } + +/** + * @file setup.js - Functions for setting up a player without + * user interaction based on the data-setup `attribute` of the video tag. + * + * @module setup + */ +var _windowLoaded = false; +var videojs = void 0; + +/** + * Set up any tags that have a data-setup `attribute` when the player is started. + */ +var autoSetup = function autoSetup() { + + // Protect against breakage in non-browser environments. + if (!Dom.isReal()) { + return; + } + + // 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 = _document2['default'].getElementsByTagName('video'); + var audios = _document2['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 _i2 = 0, _e2 = mediaEls.length; _i2 < _e2; _i2++) { + var mediaEl = mediaEls[_i2]; + + // 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. + 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); + } +}; + +/** + * Wait until the page is loaded before running autoSetup. This will be called in + * autoSetup if `hasLoaded` returns false. + * + * @param {number} wait + * How long to wait in ms + * + * @param {module:videojs} [vjs] + * The videojs library function + */ +function autoSetupTimeout(wait, vjs) { + if (vjs) { + videojs = vjs; + } + + _window2['default'].setTimeout(autoSetup, wait); +} + +if (Dom.isReal() && _document2['default'].readyState === 'complete') { + _windowLoaded = true; +} else { + /** + * Listen for the load event on window, and set _windowLoaded to true. + * + * @listens load + */ + Events.one(_window2['default'], 'load', function () { + _windowLoaded = true; + }); +} + +/** + * check if the document has been loaded + */ +var hasLoaded = function hasLoaded() { + return _windowLoaded; +}; + +exports.autoSetup = autoSetup; +exports.autoSetupTimeout = autoSetupTimeout; +exports.hasLoaded = hasLoaded; + +},{"100":100,"85":85,"86":86,"99":99}],60:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _dom = _dereq_(85); + +var Dom = _interopRequireWildcard(_dom); + +var _obj = _dereq_(93); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file slider.js + */ + + +/** + * The base functionality for a slider. Can be vertical or horizontal. + * For instance the volume bar or the seek bar on a video is a slider. + * + * @extends Component + */ +var Slider = function (_Component) { + _inherits(Slider, _Component); + + /** + * Create an instance of this class + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function Slider(player, options) { + _classCallCheck(this, Slider); + + // Set property names to bar to match with the child Slider class is looking for + var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); + + _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); + + if (_this.playerEvent) { + _this.on(player, _this.playerEvent, _this.update); + } + return _this; + } + + /** + * Create the `Button`s DOM element. + * + * @param {string} type + * Type of element to create. + * + * @param {Object} [props={}] + * List of properties in Object form. + * + * @param {Object} [attributes={}] + * list of attributes in Object form. + * + * @return {Element} + * The element that gets created. + */ + + + 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 = (0, _obj.assign)({ + tabIndex: 0 + }, props); + + attributes = (0, _obj.assign)({ + 'role': 'slider', + 'aria-valuenow': 0, + 'aria-valuemin': 0, + 'aria-valuemax': 100, + 'tabIndex': 0 + }, attributes); + + return _Component.prototype.createEl.call(this, type, props, attributes); + }; + + /** + * Handle `mousedown` or `touchstart` events on the `Slider`. + * + * @param {EventTarget~Event} event + * `mousedown` or `touchstart` event that triggered this function + * + * @listens mousedown + * @listens touchstart + * @fires Slider#slideractive + */ + + + Slider.prototype.handleMouseDown = function handleMouseDown(event) { + var doc = this.bar.el_.ownerDocument; + + event.preventDefault(); + Dom.blockTextSelection(); + + this.addClass('vjs-sliding'); + /** + * Triggered when the slider is in an active state + * + * @event Slider#slideractive + * @type {EventTarget~Event} + */ + 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); + }; + + /** + * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`. + * The `mousemove` and `touchmove` events will only only trigger this function during + * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and + * {@link Slider#handleMouseUp}. + * + * @param {EventTarget~Event} event + * `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered + * this function + * + * @listens mousemove + * @listens touchmove + */ + + + Slider.prototype.handleMouseMove = function handleMouseMove(event) {}; + + /** + * Handle `mouseup` or `touchend` events on the `Slider`. + * + * @param {EventTarget~Event} event + * `mouseup` or `touchend` event that triggered this function. + * + * @listens touchend + * @listens mouseup + * @fires Slider#sliderinactive + */ + + + Slider.prototype.handleMouseUp = function handleMouseUp() { + var doc = this.bar.el_.ownerDocument; + + Dom.unblockTextSelection(); + + this.removeClass('vjs-sliding'); + /** + * Triggered when the slider is no longer in an active state. + * + * @event Slider#sliderinactive + * @type {EventTarget~Event} + */ + 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 the progress bar of the `Slider`. + * + * @returns {number} + * The percentage of progress the progress bar represents as a + * number from 0 to 1. + */ + + + 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) + '%'; + var style = bar.el().style; + + // Set the new bar width or height + if (this.vertical()) { + style.height = percentage; + } else { + style.width = percentage; + } + + return progress; + }; + + /** + * Calculate distance for slider + * + * @param {EventTarget~Event} event + * The event that caused this function to run. + * + * @return {number} + * The current position of the Slider. + * - postition.x for vertical `Slider`s + * - postition.y for horizontal `Slider`s + */ + + + Slider.prototype.calculateDistance = function calculateDistance(event) { + var position = Dom.getPointerPosition(this.el_, event); + + if (this.vertical()) { + return position.y; + } + return position.x; + }; + + /** + * Handle a `focus` event on this `Slider`. + * + * @param {EventTarget~Event} event + * The `focus` event that caused this function to run. + * + * @listens focus + */ + + + Slider.prototype.handleFocus = function handleFocus() { + this.on(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress); + }; + + /** + * Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down + * arrow keys. This function will only be called when the slider has focus. See + * {@link Slider#handleFocus} and {@link Slider#handleBlur}. + * + * @param {EventTarget~Event} event + * the `keydown` event that caused this function to run. + * + * @listens keydown + */ + + + Slider.prototype.handleKeyPress = function handleKeyPress(event) { + // Left and Down Arrows + if (event.which === 37 || event.which === 40) { + event.preventDefault(); + this.stepBack(); + + // Up and Right Arrows + } else if (event.which === 38 || event.which === 39) { + event.preventDefault(); + this.stepForward(); + } + }; + + /** + * Handle a `blur` event on this `Slider`. + * + * @param {EventTarget~Event} event + * The `blur` event that caused this function to run. + * + * @listens blur + */ + + 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 that caused this object to run + */ + + + 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, and getting + * - false if the slider is horizontal, and getting + */ + + + 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 Slider; +}(_component2['default']); + +_component2['default'].registerComponent('Slider', Slider); +exports['default'] = Slider; + +},{"5":5,"85":85,"93":93}],61:[function(_dereq_,module,exports){ +'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.']); + +var _tech = _dereq_(64); + +var _tech2 = _interopRequireDefault(_tech); + +var _dom = _dereq_(85); + +var Dom = _interopRequireWildcard(_dom); + +var _url = _dereq_(97); + +var Url = _interopRequireWildcard(_url); + +var _log = _dereq_(91); + +var _log2 = _interopRequireDefault(_log); + +var _tsml = _dereq_(103); + +var _tsml2 = _interopRequireDefault(_tsml); + +var _browser = _dereq_(81); + +var browser = _interopRequireWildcard(_browser); + +var _document = _dereq_(99); + +var _document2 = _interopRequireDefault(_document); + +var _window = _dereq_(100); + +var _window2 = _interopRequireDefault(_window); + +var _obj = _dereq_(93); + +var _mergeOptions = _dereq_(92); + +var _mergeOptions2 = _interopRequireDefault(_mergeOptions); + +var _toTitleCase = _dereq_(96); + +var _toTitleCase2 = _interopRequireDefault(_toTitleCase); + +var _trackTypes = _dereq_(77); + +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; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file html5.js + */ + + +/** + * HTML5 Media Controller - Wrapper for HTML5 Media API + * + * @mixes Tech~SouceHandlerAdditions + * @extends Tech + */ +var Html5 = function (_Tech) { + _inherits(Html5, _Tech); + + /** + * Create an instance of this Tech. + * + * @param {Object} [options] + * The key/value store of player options. + * + * @param {Component~ReadyCallback} ready + * Callback function to call when the `HTML5` Tech is ready. + */ + function Html5(options, ready) { + _classCallCheck(this, Html5); + + var _this = _possibleConstructorReturn(this, _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); + _this.textTracks().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]); + } + } + + _this.proxyNativeTracks_(); + if (_this.featuresNativeTextTracks && crossoriginTracks) { + _log2['default'].warn((0, _tsml2['default'])(_templateObject)); + } + + // 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 || browser.IS_IPHONE || browser.IS_NATIVE_ANDROID) && options.nativeControlsForTouch === true) { + _this.setControls(true); + } + + // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen` + // into a `fullscreenchange` event + _this.proxyWebkitFullscreen_(); + + _this.triggerReady(); + return _this; + } + + /** + * Dispose of `HTML5` media element and remove all tracks. + */ + + + Html5.prototype.dispose = function dispose() { + Html5.disposeMediaElement(this.el_); + // tech will handle clearing of the emulated track list + _Tech.prototype.dispose.call(this); + }; + + /** + * Proxy all native track list events to our track lists if the browser we are playing + * in supports that type of track list. + * + * @private + */ + + + Html5.prototype.proxyNativeTracks_ = function proxyNativeTracks_() { + var _this2 = this; + + _trackTypes.NORMAL.names.forEach(function (name) { + var props = _trackTypes.NORMAL[name]; + var elTracks = _this2.el()[props.getterName]; + var techTracks = _this2[props.getterName](); + + if (!_this2['featuresNative' + props.capitalName + 'Tracks'] || !elTracks || !elTracks.addEventListener) { + return; + } + var listeners = { + change: function change(e) { + techTracks.trigger({ + type: 'change', + target: techTracks, + currentTarget: techTracks, + srcElement: techTracks + }); + }, + addtrack: function addtrack(e) { + techTracks.addTrack(e.track); + }, + removetrack: function removetrack(e) { + techTracks.removeTrack(e.track); + } + }; + var removeOldTracks = function removeOldTracks() { + var removeTracks = []; + + for (var i = 0; i < techTracks.length; i++) { + var found = false; + + for (var j = 0; j < elTracks.length; j++) { + if (elTracks[j] === techTracks[i]) { + found = true; + break; + } + } + + if (!found) { + removeTracks.push(techTracks[i]); + } + } + + while (removeTracks.length) { + techTracks.removeTrack(removeTracks.shift()); + } + }; + + Object.keys(listeners).forEach(function (eventName) { + var listener = listeners[eventName]; + + elTracks.addEventListener(eventName, listener); + _this2.on('dispose', function (e) { + return elTracks.removeEventListener(eventName, listener); + }); + }); + + // Remove (native) tracks that are not used anymore + _this2.on('loadstart', removeOldTracks); + _this2.on('dispose', function (e) { + return _this2.off('loadstart', removeOldTracks); + }); + }); + }; + + /** + * Create the `Html5` Tech's DOM element. + * + * @return {Element} + * The element that gets created. + */ + + + 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 we ingested the player div, we do not need to move the media element. + if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) { + + // If the original tag is still there, clone and remove it. + if (el) { + var clone = el.cloneNode(true); + + if (el.parentNode) { + el.parentNode.insertBefore(clone, el); + } + Html5.disposeMediaElement(el); + el = clone; + } else { + el = _document2['default'].createElement('video'); + + // determine if native controls should be used + var tagAttributes = this.options_.tag && Dom.getAttributes(this.options_.tag); + var attributes = (0, _mergeOptions2['default'])({}, tagAttributes); + + if (!browser.TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) { + delete attributes.controls; + } + + Dom.setAttributes(el, (0, _obj.assign)(attributes, { + id: this.options_.techId, + 'class': 'vjs-tech' + })); + } + + el.playerId = this.options_.playerId; + } + + // Update specific tag settings, in case they were overridden + var settingsAttrs = ['autoplay', 'preload', 'loop', 'muted', 'playsinline']; + + 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.setAttributes(el, overwriteAttrs); + } + + return el; + }; + + /** + * This will be triggered if the loadstart event has already fired, before videojs was + * ready. Two known examples of when this can happen are: + * 1. If we're loading the playback object after it has started loading + * 2. The media is already playing the (often with autoplay on) then + * + * This function will fire another loadstart so that videojs can catchup. + * + * @fires Tech#loadstart + * + * @return {undefined} + * returns nothing. + */ + + + Html5.prototype.handleLateInit_ = function handleLateInit_(el) { + 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) { + // 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; + }; + + this.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'); + } + }; + + this.on('loadedmetadata', triggerLoadstart); + + this.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; + } + + // 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); + }); + }; + + /** + * Set current time for the `HTML5` tech. + * + * @param {number} seconds + * Set the current time of the media to this. + */ + + + Html5.prototype.setCurrentTime = function setCurrentTime(seconds) { + try { + this.el_.currentTime = seconds; + } catch (e) { + (0, _log2['default'])(e, 'Video is not ready. (Video.js)'); + // this.warning(VideoJS.warnings.videoNotReady); + } + }; + + /** + * Get the current duration of the HTML5 media element. + * + * @return {number} + * The duration of the media or 0 if there is no duration. + */ + + + Html5.prototype.duration = function duration() { + var _this3 = this; + + // Android Chrome will report duration as Infinity for VOD HLS until after + // playback has started, which triggers the live display erroneously. + // Return NaN if playback has not started and trigger a durationupdate once + // the duration can be reliably known. + if (this.el_.duration === Infinity && browser.IS_ANDROID && browser.IS_CHROME) { + if (this.el_.currentTime === 0) { + // Wait for the first `timeupdate` with currentTime > 0 - there may be + // several with 0 + var checkProgress = function checkProgress() { + if (_this3.el_.currentTime > 0) { + // Trigger durationchange for genuinely live video + if (_this3.el_.duration === Infinity) { + _this3.trigger('durationchange'); + } + _this3.off('timeupdate', checkProgress); + } + }; + + this.on('timeupdate', checkProgress); + return NaN; + } + } + return this.el_.duration || NaN; + }; + + /** + * Get the current width of the HTML5 media element. + * + * @return {number} + * The width of the HTML5 media element. + */ + + + Html5.prototype.width = function width() { + return this.el_.offsetWidth; + }; + + /** + * Get the current height of the HTML5 media element. + * + * @return {number} + * The heigth of the HTML5 media element. + */ + + + Html5.prototype.height = function height() { + return this.el_.offsetHeight; + }; + + /** + * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into + * `fullscreenchange` event. + * + * @private + * @fires fullscreenchange + * @listens webkitendfullscreen + * @listens webkitbeginfullscreen + * @listens webkitbeginfullscreen + */ + + + Html5.prototype.proxyWebkitFullscreen_ = function proxyWebkitFullscreen_() { + var _this4 = this; + + if (!('webkitDisplayingFullscreen' in this.el_)) { + return; + } + + var endFn = function endFn() { + this.trigger('fullscreenchange', { isFullscreen: false }); + }; + + var beginFn = function beginFn() { + this.one('webkitendfullscreen', endFn); + + this.trigger('fullscreenchange', { isFullscreen: true }); + }; + + this.on('webkitbeginfullscreen', beginFn); + this.on('dispose', function () { + _this4.off('webkitbeginfullscreen', beginFn); + _this4.off('webkitendfullscreen', endFn); + }); + }; + + /** + * Check if fullscreen is supported on the current playback device. + * + * @return {boolean} + * - True if fullscreen is supported. + * - False if fullscreen is not supported. + */ + + + Html5.prototype.supportsFullScreen = function supportsFullScreen() { + if (typeof this.el_.webkitEnterFullScreen === 'function') { + var userAgent = _window2['default'].navigator && _window2['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 that the `HTML5` Tech enter fullscreen. + */ + + + Html5.prototype.enterFullScreen = function enterFullScreen() { + var video = this.el_; + + 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 that the `HTML5` Tech exit fullscreen. + */ + + + Html5.prototype.exitFullScreen = function exitFullScreen() { + this.el_.webkitExitFullScreen(); + }; + + /** + * A getter/setter for the `Html5` Tech's source object. + * > Note: Please use {@link Html5#setSource} + * + * @param {Tech~SourceObject} [src] + * The source object you want to set on the `HTML5` techs element. + * + * @return {Tech~SourceObject|undefined} + * - The current source object when a source is not passed in. + * - undefined when setting + * + * @deprecated Since version 5. + */ + + + Html5.prototype.src = function src(_src) { + if (_src === undefined) { + return this.el_.src; + } + + // Setting src through `src` instead of `setSrc` will be deprecated + this.setSrc(_src); + }; + + /** + * Reset the tech by removing all sources and then calling + * {@link Html5.resetMediaElement}. + */ + + + Html5.prototype.reset = function reset() { + Html5.resetMediaElement(this.el_); + }; + + /** + * Get the current source on the `HTML5` Tech. Falls back to returning the source from + * the HTML5 media element. + * + * @return {Tech~SourceObject} + * The current source object from the HTML5 tech. With a fallback to the + * elements source. + */ + + + Html5.prototype.currentSrc = function currentSrc() { + if (this.currentSource_) { + return this.currentSource_.src; + } + return this.el_.currentSrc; + }; + + /** + * Set controls attribute for the HTML5 media Element. + * + * @param {string} val + * Value to set the controls attribute to + */ + + + Html5.prototype.setControls = function setControls(val) { + this.el_.controls = !!val; + }; + + /** + * Create and returns a remote {@link TextTrack} object. + * + * @param {string} kind + * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) + * + * @param {string} [label] + * Label to identify the text track + * + * @param {string} [language] + * Two letter language abbreviation + * + * @return {TextTrack} + * The TextTrack that gets created. + */ + + + 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 either native TextTrack or an emulated TextTrack depending + * on the value of `featuresNativeTextTracks` + * + * @param {Object} options + * The object should contain the options to intialize the TextTrack with. + * + * @param {string} [options.kind] + * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata). + * + * @param {string} [options.label]. + * Label to identify the text track + * + * @param {string} [options.language] + * Two letter language abbreviation. + * + * @param {boolean} [options.default] + * Default this track to on. + * + * @param {string} [options.id] + * The internal id to assign this track. + * + * @param {string} [options.src] + * A source url for the track. + * + * @return {HTMLTrackElement} + * The track element that gets created. + */ + + + Html5.prototype.createRemoteTextTrack = function createRemoteTextTrack(options) { + if (!this.featuresNativeTextTracks) { + return _Tech.prototype.createRemoteTextTrack.call(this, options); + } + var htmlTrackElement = _document2['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; + } + + return htmlTrackElement; + }; + + /** + * Creates a remote text track object and returns an html track element. + * + * @param {Object} options The object should contain values for + * kind, language, label, and src (location of the WebVTT file) + * @param {Boolean} [manualCleanup=true] if set to false, the TextTrack will be + * automatically removed from the video element whenever the source changes + * @return {HTMLTrackElement} An Html Track Element. + * This can be an emulated {@link HTMLTrackElement} or a native one. + * @deprecated The default value of the "manualCleanup" parameter will default + * to "false" in upcoming versions of Video.js + */ + + + Html5.prototype.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { + var htmlTrackElement = _Tech.prototype.addRemoteTextTrack.call(this, options, manualCleanup); + + if (this.featuresNativeTextTracks) { + this.el().appendChild(htmlTrackElement); + } + + return htmlTrackElement; + }; + + /** + * Remove remote `TextTrack` from `TextTrackList` object + * + * @param {TextTrack} track + * `TextTrack` object to remove + */ + + + Html5.prototype.removeRemoteTextTrack = function removeRemoteTextTrack(track) { + _Tech.prototype.removeRemoteTextTrack.call(this, track); + + if (this.featuresNativeTextTracks) { + var tracks = this.$$('track'); + + var i = tracks.length; + + while (i--) { + if (track === tracks[i] || track === tracks[i].track) { + this.el().removeChild(tracks[i]); + } + } + } + }; + + /** + * Get the value of `playsinline` from the media element. `playsinline` indicates + * to the browser that non-fullscreen playback is preferred when fullscreen + * playback is the native default, such as in iOS Safari. + * + * @method Html5#playsinline + * @return {boolean} + * - The value of `playsinline` from the media element. + * - True indicates that the media should play inline. + * - False indicates that the media should not play inline. + * + * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} + */ + + + Html5.prototype.playsinline = function playsinline() { + return this.el_.hasAttribute('playsinline'); + }; + + /** + * Set the value of `playsinline` from the media element. `playsinline` indicates + * to the browser that non-fullscreen playback is preferred when fullscreen + * playback is the native default, such as in iOS Safari. + * + * @method Html5#setPlaysinline + * @param {boolean} playsinline + * - True indicates that the media should play inline. + * - False indicates that the media should not play inline. + * + * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} + */ + + + Html5.prototype.setPlaysinline = function setPlaysinline(value) { + if (value) { + this.el_.setAttribute('playsinline', 'playsinline'); + } else { + this.el_.removeAttribute('playsinline'); + } + }; + + /** + * Gets available media playback quality metrics as specified by the W3C's Media + * Playback Quality API. + * + * @see [Spec]{@link https://wicg.github.io/media-playback-quality} + * + * @return {Object} + * An object with supported media playback quality metrics + */ + + + Html5.prototype.getVideoPlaybackQuality = function getVideoPlaybackQuality() { + if (typeof this.el().getVideoPlaybackQuality === 'function') { + return this.el().getVideoPlaybackQuality(); + } + + var videoPlaybackQuality = {}; + + if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') { + videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount; + videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount; + } + + if (_window2['default'].performance && typeof _window2['default'].performance.now === 'function') { + videoPlaybackQuality.creationTime = _window2['default'].performance.now(); + } else if (_window2['default'].performance && _window2['default'].performance.timing && typeof _window2['default'].performance.timing.navigationStart === 'number') { + videoPlaybackQuality.creationTime = _window2['default'].Date.now() - _window2['default'].performance.timing.navigationStart; + } + + return videoPlaybackQuality; + }; + + return Html5; +}(_tech2['default']); + +/* HTML5 Support Testing ---------------------------------------------------- */ + +if (Dom.isReal()) { + + /** + * Element for testing browser HTML5 media capabilities + * + * @type {Element} + * @constant + * @private + */ + Html5.TEST_VID = _document2['default'].createElement('video'); + var track = _document2['default'].createElement('track'); + + track.kind = 'captions'; + track.srclang = 'en'; + track.label = 'English'; + Html5.TEST_VID.appendChild(track); +} + +/** + * Check if HTML5 media is supported by this browser/device. + * + * @return {boolean} + * - True if HTML5 media is supported. + * - False if HTML5 media is not supported. + */ +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 && Html5.TEST_VID.canPlayType); +}; + +/** + * Check if the tech can support the given type + * + * @param {string} type + * The mimetype to check + * @return {string} 'probably', 'maybe', or '' (empty string) + */ +Html5.canPlayType = function (type) { + return Html5.TEST_VID.canPlayType(type); +}; + +/** + * 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) + */ +Html5.canPlaySource = function (srcObj, options) { + return Html5.canPlayType(srcObj.type); +}; + +/** + * 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} + * - True if volume can be controlled + * - False otherwise + */ +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 the playback rate can be changed in this browser/device. + * + * @return {boolean} + * - True if playback rate can be controlled + * - False otherwise + */ +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 && browser.CHROME_VERSION < 58) { + 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 `TextTrack`s are supported by this browser/device. + * + * @return {boolean} + * - True if native `TextTrack`s are supported. + * - False otherwise + */ +Html5.supportsNativeTextTracks = function () { + return browser.IS_ANY_SAFARI; +}; + +/** + * Check to see if native `VideoTrack`s are supported by this browser/device + * + * @return {boolean} + * - True if native `VideoTrack`s are supported. + * - False otherwise + */ +Html5.supportsNativeVideoTracks = function () { + return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks); +}; + +/** + * Check to see if native `AudioTrack`s are supported by this browser/device + * + * @return {boolean} + * - True if native `AudioTrack`s are supported. + * - False otherwise + */ +Html5.supportsNativeAudioTracks = function () { + return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks); +}; + +/** + * 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', 'resize', 'volumechange']; + +/** + * Boolean indicating whether the `Tech` supports volume control. + * + * @type {boolean} + * @default {@link Html5.canControlVolume} + */ +Html5.prototype.featuresVolumeControl = Html5.canControlVolume(); + +/** + * Boolean indicating whether the `Tech` supports changing the speed at which the media + * plays. Examples: + * - Set player to play 2x (twice) as fast + * - Set player to play 0.5x (half) as fast + * + * @type {boolean} + * @default {@link Html5.canControlPlaybackRate} + */ +Html5.prototype.featuresPlaybackRate = Html5.canControlPlaybackRate(); + +/** + * Boolean indicating whether the `HTML5` tech currently supports the media element + * moving in the DOM. iOS breaks if you move the media element, so this is set this to + * false there. Everywhere else this should be true. + * + * @type {boolean} + * @default + */ +Html5.prototype.movingMediaElementInDOM = !browser.IS_IOS; + +// TODO: Previous comment: No longer appears to be used. Can probably be removed. +// Is this true? +/** + * Boolean indicating whether the `HTML5` tech currently supports automatic media resize + * when going into fullscreen. + * + * @type {boolean} + * @default + */ +Html5.prototype.featuresFullscreenResize = true; + +/** + * Boolean indicating whether the `HTML5` tech currently supports the progress event. + * If this is false, manual `progress` events will be triggred instead. + * + * @type {boolean} + * @default + */ +Html5.prototype.featuresProgressEvents = true; + +/** + * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event. + * If this is false, manual `timeupdate` events will be triggred instead. + * + * @default + */ +Html5.prototype.featuresTimeupdateEvents = true; + +/** + * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s. + * + * @type {boolean} + * @default {@link Html5.supportsNativeTextTracks} + */ +Html5.prototype.featuresNativeTextTracks = Html5.supportsNativeTextTracks(); + +/** + * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s. + * + * @type {boolean} + * @default {@link Html5.supportsNativeVideoTracks} + */ +Html5.prototype.featuresNativeVideoTracks = Html5.supportsNativeVideoTracks(); + +/** + * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s. + * + * @type {boolean} + * @default {@link Html5.supportsNativeAudioTracks} + */ +Html5.prototype.featuresNativeAudioTracks = Html5.supportsNativeAudioTracks(); + +// HTML5 Feature detection and Device Fixes --------------------------------- // +var canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType; +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 && !browser.IS_FIREFOX) { + 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 + } else if (browser.IS_OLD_ANDROID) { + 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; + return r; +}; + +// by default, patch the media 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) { + // satisfy linter + } + })(); + } +}; + +/* Native HTML5 element property wrapping ----------------------------------- */ +// Wrap native properties with a getter +[ +/** + * Get the value of `paused` from the media element. `paused` indicates whether the media element + * is currently paused or not. + * + * @method Html5#paused + * @return {boolean} + * The value of `paused` from the media element. + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused} + */ +'paused', + +/** + * Get the value of `currentTime` from the media element. `currentTime` indicates + * the current second that the media is at in playback. + * + * @method Html5#currentTime + * @return {number} + * The value of `currentTime` from the media element. + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime} + */ +'currentTime', + +/** + * Get the value of `buffered` from the media element. `buffered` is a `TimeRange` + * object that represents the parts of the media that are already downloaded and + * available for playback. + * + * @method Html5#buffered + * @return {TimeRange} + * The value of `buffered` from the media element. + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered} + */ +'buffered', + +/** + * Get the value of `volume` from the media element. `volume` indicates + * the current playback volume of audio for a media. `volume` will be a value from 0 + * (silent) to 1 (loudest and default). + * + * @method Html5#volume + * @return {number} + * The value of `volume` from the media element. Value will be between 0-1. + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume} + */ +'volume', + +/** + * Get the value of `muted` from the media element. `muted` indicates + * that the volume for the media should be set to silent. This does not actually change + * the `volume` attribute. + * + * @method Html5#muted + * @return {boolean} + * - True if the value of `volume` should be ignored and the audio set to silent. + * - False if the value of `volume` should be used. + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted} + */ +'muted', + +/** + * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates + * that the volume for the media should be set to silent when the video first starts. + * This does not actually change the `volume` attribute. After playback has started `muted` + * will indicate the current status of the volume and `defaultMuted` will not. + * + * @method Html5.prototype.defaultMuted + * @return {boolean} + * - True if the value of `volume` should be ignored and the audio set to silent. + * - False if the value of `volume` should be used. + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted} + */ +'defaultMuted', + +/** + * Get the value of `poster` from the media element. `poster` indicates + * that the url of an image file that can/will be shown when no media data is available. + * + * @method Html5#poster + * @return {string} + * The value of `poster` from the media element. Value will be a url to an + * image. + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster} + */ +'poster', + +/** + * Get the value of `preload` from the media element. `preload` indicates + * what should download before the media is interacted with. It can have the following + * values: + * - none: nothing should be downloaded + * - metadata: poster and the first few frames of the media may be downloaded to get + * media dimensions and other metadata + * - auto: allow the media and metadata for the media to be downloaded before + * interaction + * + * @method Html5#preload + * @return {string} + * The value of `preload` from the media element. Will be 'none', 'metadata', + * or 'auto'. + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload} + */ +'preload', + +/** + * Get the value of `autoplay` from the media element. `autoplay` indicates + * that the media should start to play as soon as the page is ready. + * + * @method Html5#autoplay + * @return {boolean} + * - The value of `autoplay` from the media element. + * - True indicates that the media should start as soon as the page loads. + * - False indicates that the media should not start as soon as the page loads. + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay} + */ +'autoplay', + +/** + * Get the value of `controls` from the media element. `controls` indicates + * whether the native media controls should be shown or hidden. + * + * @method Html5#controls + * @return {boolean} + * - The value of `controls` from the media element. + * - True indicates that native controls should be showing. + * - False indicates that native controls should be hidden. + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls} + */ +'controls', + +/** + * Get the value of `loop` from the media element. `loop` indicates + * that the media should return to the start of the media and continue playing once + * it reaches the end. + * + * @method Html5#loop + * @return {boolean} + * - The value of `loop` from the media element. + * - True indicates that playback should seek back to start once + * the end of a media is reached. + * - False indicates that playback should not loop back to the start when the + * end of the media is reached. + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop} + */ +'loop', + +/** + * Get the value of the `error` from the media element. `error` indicates any + * MediaError that may have occured during playback. If error returns null there is no + * current error. + * + * @method Html5#error + * @return {MediaError|null} + * The value of `error` from the media element. Will be `MediaError` if there + * is a current error and null otherwise. + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error} + */ +'error', + +/** + * Get the value of `seeking` from the media element. `seeking` indicates whether the + * media is currently seeking to a new position or not. + * + * @method Html5#seeking + * @return {boolean} + * - The value of `seeking` from the media element. + * - True indicates that the media is currently seeking to a new position. + * - Flase indicates that the media is not seeking to a new position at this time. + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking} + */ +'seeking', + +/** + * Get the value of `seekable` from the media element. `seekable` returns a + * `TimeRange` object indicating ranges of time that can currently be `seeked` to. + * + * @method Html5#seekable + * @return {TimeRange} + * The value of `seekable` from the media element. A `TimeRange` object + * indicating the current ranges of time that can be seeked to. + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable} + */ +'seekable', + +/** + * Get the value of `ended` from the media element. `ended` indicates whether + * the media has reached the end or not. + * + * @method Html5#ended + * @return {boolean} + * - The value of `ended` from the media element. + * - True indicates that the media has ended. + * - False indicates that the media has not ended. + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended} + */ +'ended', + +/** + * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates + * whether the media should start muted or not. Only changes the default state of the + * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the + * current state. + * + * @method Html5#defaultMuted + * @return {boolean} + * - The value of `defaultMuted` from the media element. + * - True indicates that the media should start muted. + * - False indicates that the media should not start muted + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted} + */ +'defaultMuted', + +/** + * Get the value of `playbackRate` from the media element. `playbackRate` indicates + * the rate at which the media is currently playing back. Examples: + * - if playbackRate is set to 2, media will play twice as fast. + * - if playbackRate is set to 0.5, media will play half as fast. + * + * @method Html5#playbackRate + * @return {number} + * The value of `playbackRate` from the media element. A number indicating + * the current playback speed of the media, where 1 is normal speed. + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} + */ +'playbackRate', + +/** + * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates + * the rate at which the media is currently playing back. This value will not indicate the current + * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that. + * + * Examples: + * - if defaultPlaybackRate is set to 2, media will play twice as fast. + * - if defaultPlaybackRate is set to 0.5, media will play half as fast. + * + * @method Html5.prototype.defaultPlaybackRate + * @return {number} + * The value of `defaultPlaybackRate` from the media element. A number indicating + * the current playback speed of the media, where 1 is normal speed. + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} + */ +'defaultPlaybackRate', + +/** + * Get the value of `played` from the media element. `played` returns a `TimeRange` + * object representing points in the media timeline that have been played. + * + * @method Html5#played + * @return {TimeRange} + * The value of `played` from the media element. A `TimeRange` object indicating + * the ranges of time that have been played. + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played} + */ +'played', + +/** + * Get the value of `networkState` from the media element. `networkState` indicates + * the current network state. It returns an enumeration from the following list: + * - 0: NETWORK_EMPTY + * - 1: NEWORK_IDLE + * - 2: NETWORK_LOADING + * - 3: NETWORK_NO_SOURCE + * + * @method Html5#networkState + * @return {number} + * The value of `networkState` from the media element. This will be a number + * from the list in the description. + * + * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate} + */ +'networkState', + +/** + * Get the value of `readyState` from the media element. `readyState` indicates + * the current state of the media element. It returns an enumeration from the + * following list: + * - 0: HAVE_NOTHING + * - 1: HAVE_METADATA + * - 2: HAVE_CURRENT_DATA + * - 3: HAVE_FUTURE_DATA + * - 4: HAVE_ENOUGH_DATA + * + * @method Html5#readyState + * @return {number} + * The value of `readyState` from the media element. This will be a number + * from the list in the description. + * + * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states} + */ +'readyState', + +/** + * Get the value of `videoWidth` from the video element. `videoWidth` indicates + * the current width of the video in css pixels. + * + * @method Html5#videoWidth + * @return {number} + * The value of `videoWidth` from the video element. This will be a number + * in css pixels. + * + * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth} + */ +'videoWidth', + +/** + * Get the value of `videoHeight` from the video element. `videoHeigth` indicates + * the current height of the video in css pixels. + * + * @method Html5#videoHeight + * @return {number} + * The value of `videoHeight` from the video element. This will be a number + * in css pixels. + * + * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth} + */ +'videoHeight'].forEach(function (prop) { + Html5.prototype[prop] = function () { + return this.el_[prop]; + }; +}); + +// Wrap native properties with a setter in this format: +// set + toTitleCase(name) +[ +/** + * Set the value of `volume` on the media element. `volume` indicates the current + * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and + * so on. + * + * @method Html5#setVolume + * @param {number} percentAsDecimal + * The volume percent as a decimal. Valid range is from 0-1. + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume} + */ +'volume', + +/** + * Set the value of `muted` on the media element. `muted` indicates that the current + * audio level should be silent. + * + * @method Html5#setMuted + * @param {boolean} muted + * - True if the audio should be set to silent + * - False otherwise + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted} + */ +'muted', + +/** + * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current + * audio level should be silent, but will only effect the muted level on intial playback.. + * + * @method Html5.prototype.setDefaultMuted + * @param {boolean} defaultMuted + * - True if the audio should be set to silent + * - False otherwise + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted} + */ +'defaultMuted', + +/** + * Set the value of `src` on the media element. `src` indicates the current + * {@link Tech~SourceObject} for the media. + * + * @method Html5#setSrc + * @param {Tech~SourceObject} src + * The source object to set as the current source. + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src} + */ +'src', + +/** + * Set the value of `poster` on the media element. `poster` is the url to + * an image file that can/will be shown when no media data is available. + * + * @method Html5#setPoster + * @param {string} poster + * The url to an image that should be used as the `poster` for the media + * element. + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster} + */ +'poster', + +/** + * Set the value of `preload` on the media element. `preload` indicates + * what should download before the media is interacted with. It can have the following + * values: + * - none: nothing should be downloaded + * - metadata: poster and the first few frames of the media may be downloaded to get + * media dimensions and other metadata + * - auto: allow the media and metadata for the media to be downloaded before + * interaction + * + * @method Html5#setPreload + * @param {string} preload + * The value of `preload` to set on the media element. Must be 'none', 'metadata', + * or 'auto'. + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload} + */ +'preload', + +/** + * Set the value of `autoplay` on the media element. `autoplay` indicates + * that the media should start to play as soon as the page is ready. + * + * @method Html5#setAutoplay + * @param {boolean} autoplay + * - True indicates that the media should start as soon as the page loads. + * - False indicates that the media should not start as soon as the page loads. + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay} + */ +'autoplay', + +/** + * Set the value of `loop` on the media element. `loop` indicates + * that the media should return to the start of the media and continue playing once + * it reaches the end. + * + * @method Html5#setLoop + * @param {boolean} loop + * - True indicates that playback should seek back to start once + * the end of a media is reached. + * - False indicates that playback should not loop back to the start when the + * end of the media is reached. + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop} + */ +'loop', + +/** + * Set the value of `playbackRate` on the media element. `playbackRate` indicates + * the rate at which the media should play back. Examples: + * - if playbackRate is set to 2, media will play twice as fast. + * - if playbackRate is set to 0.5, media will play half as fast. + * + * @method Html5#setPlaybackRate + * @return {number} + * The value of `playbackRate` from the media element. A number indicating + * the current playback speed of the media, where 1 is normal speed. + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} + */ +'playbackRate', + +/** + * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates + * the rate at which the media should play back upon initial startup. Changing this value + * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}. + * + * Example Values: + * - if playbackRate is set to 2, media will play twice as fast. + * - if playbackRate is set to 0.5, media will play half as fast. + * + * @method Html5.prototype.setDefaultPlaybackRate + * @return {number} + * The value of `defaultPlaybackRate` from the media element. A number indicating + * the current playback speed of the media, where 1 is normal speed. + * + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate} + */ +'defaultPlaybackRate'].forEach(function (prop) { + Html5.prototype['set' + (0, _toTitleCase2['default'])(prop)] = function (v) { + this.el_[prop] = v; + }; +}); + +// wrap native functions with a function +[ +/** + * A wrapper around the media elements `pause` function. This will call the `HTML5` + * media elements `pause` function. + * + * @method Html5#pause + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause} + */ +'pause', + +/** + * A wrapper around the media elements `load` function. This will call the `HTML5`s + * media element `load` function. + * + * @method Html5#load + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load} + */ +'load', + +/** + * A wrapper around the media elements `play` function. This will call the `HTML5`s + * media element `play` function. + * + * @method Html5#play + * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play} + */ +'play'].forEach(function (prop) { + Html5.prototype[prop] = function () { + return this.el_[prop](); + }; +}); + +_tech2['default'].withSourceHandlers(Html5); + +/** + * Native source handler for Html5, simply passes the source to the media element. + * + * @proprety {Tech~SourceObject} source + * The source object + * + * @proprety {Html5} tech + * The instance of the HTML5 tech. + */ +Html5.nativeSourceHandler = {}; + +/** + * Check if the media element can play the given mime type. + * + * @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 media element can handle a source natively. + * + * @param {Tech~SourceObject} source + * The source object + * + * @param {Object} [options] + * Options to be passed to the tech. + * + * @return {string} + * 'probably', 'maybe', or '' (empty string). + */ +Html5.nativeSourceHandler.canHandleSource = function (source, options) { + + // If a type was provided we should rely on that + if (source.type) { + return Html5.nativeSourceHandler.canPlayType(source.type); + + // If no type, fall back to checking 'video/[EXTENSION]' + } else if (source.src) { + var ext = Url.getFileExtension(source.src); + + return Html5.nativeSourceHandler.canPlayType('video/' + ext); + } + + return ''; +}; + +/** + * Pass the source to the native media element. + * + * @param {Tech~SourceObject} 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); +}; + +/** + * A noop for the native dispose function, as cleanup is not needed. + */ +Html5.nativeSourceHandler.dispose = function () {}; + +// Register the native source handler +Html5.registerSourceHandler(Html5.nativeSourceHandler); + +_tech2['default'].registerTech('Html5', Html5); +exports['default'] = Html5; + +},{"100":100,"103":103,"64":64,"77":77,"81":81,"85":85,"91":91,"92":92,"93":93,"96":96,"97":97,"99":99}],62:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _tech = _dereq_(64); + +var _tech2 = _interopRequireDefault(_tech); + +var _toTitleCase = _dereq_(96); + +var _toTitleCase2 = _interopRequireDefault(_toTitleCase); + +var _mergeOptions = _dereq_(92); + +var _mergeOptions2 = _interopRequireDefault(_mergeOptions); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file loader.js + */ + + +/** + * The `MediaLoader` is the `Component` that decides which playback technology to load + * when a player is initialized. + * + * @extends Component + */ +var MediaLoader = function (_Component) { + _inherits(MediaLoader, _Component); + + /** + * Create an instance of this class. + * + * @param {Player} player + * The `Player` that this class should attach to. + * + * @param {Object} [options] + * The key/value stroe of player options. + * + * @param {Component~ReadyCallback} [ready] + * The function that is run when this component is ready. + */ + function MediaLoader(player, options, ready) { + _classCallCheck(this, MediaLoader); + + // MediaLoader has no element + var options_ = (0, _mergeOptions2['default'])({ createEl: false }, options); + + // If there are no sources when the player is initialized, + // load the first supported playback technology. + + var _this = _possibleConstructorReturn(this, _Component.call(this, player, options_, ready)); + + if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) { + for (var i = 0, j = options.playerOptions.techOrder; i < j.length; i++) { + var techName = (0, _toTitleCase2['default'])(j[i]); + var tech = _tech2['default'].getTech(techName); + + // Support old behavior of techs being registered as components. + // Remove once that deprecated behavior is removed. + if (!techName) { + tech = _component2['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 _this; + } + + return MediaLoader; +}(_component2['default']); + +_component2['default'].registerComponent('MediaLoader', MediaLoader); +exports['default'] = MediaLoader; + +},{"5":5,"64":64,"92":92,"96":96}],63:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; +exports.allowedSetters = exports.allowedGetters = undefined; +exports.use = use; +exports.getMiddleware = getMiddleware; +exports.setSource = setSource; +exports.setTech = setTech; +exports.get = get; +exports.set = set; + +var _obj = _dereq_(93); + +var middlewares = {}; + +function use(type, middleware) { + middlewares[type] = middlewares[type] || []; + middlewares[type].push(middleware); +} + +function getMiddleware(type) { + if (type) { + return middlewares[type]; + } + + return middlewares; +} + +function setSource(player, src, next) { + player.setTimeout(function () { + return setSourceHelper(src, middlewares[src.type], next, player); + }, 1); +} + +function setTech(middleware, tech) { + middleware.forEach(function (mw) { + return mw.setTech && mw.setTech(tech); + }); +} + +function get(middleware, tech, method) { + return middleware.reduceRight(middlewareIterator(method), tech[method]()); +} + +function set(middleware, tech, method, arg) { + return tech[method](middleware.reduce(middlewareIterator(method), arg)); +} + +var allowedGetters = exports.allowedGetters = { + buffered: 1, + currentTime: 1, + duration: 1, + seekable: 1, + played: 1 +}; + +var allowedSetters = exports.allowedSetters = { + setCurrentTime: 1 +}; + +function middlewareIterator(method) { + return function (value, mw) { + if (mw[method]) { + return mw[method](value); + } + + return value; + }; +} + +function setSourceHelper() { + var src = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var middleware = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; + var next = arguments[2]; + var player = arguments[3]; + var acc = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : []; + var lastRun = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false; + var mwFactory = middleware[0], + mwrest = middleware.slice(1); + + // if mwFactory is a string, then we're at a fork in the road + + if (typeof mwFactory === 'string') { + setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun); + + // if we have an mwFactory, call it with the player to get the mw, + // then call the mw's setSource method + } else if (mwFactory) { + var mw = mwFactory(player); + + mw.setSource((0, _obj.assign)({}, src), function (err, _src) { + + // something happened, try the next middleware on the current level + // make sure to use the old src + if (err) { + return setSourceHelper(src, mwrest, next, player, acc, lastRun); + } + + // we've succeeded, now we need to go deeper + acc.push(mw); + + // if it's the same time, continue does the current chain + // otherwise, we want to go down the new chain + setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun); + }); + } else if (mwrest.length) { + setSourceHelper(src, mwrest, next, player, acc, lastRun); + } else if (lastRun) { + next(src, acc); + } else { + setSourceHelper(src, middlewares['*'], next, player, acc, true); + } +} + +},{"93":93}],64:[function(_dereq_,module,exports){ +'use strict'; + +exports.__esModule = true; + +var _component = _dereq_(5); + +var _component2 = _interopRequireDefault(_component); + +var _mergeOptions = _dereq_(92); + +var _mergeOptions2 = _interopRequireDefault(_mergeOptions); + +var _fn = _dereq_(88); + +var Fn = _interopRequireWildcard(_fn); + +var _log = _dereq_(91); + +var _log2 = _interopRequireDefault(_log); + +var _timeRanges = _dereq_(95); + +var _buffer = _dereq_(82); + +var _mediaError = _dereq_(49); + +var _mediaError2 = _interopRequireDefault(_mediaError); + +var _window = _dereq_(100); + +var _window2 = _interopRequireDefault(_window); + +var _document = _dereq_(99); + +var _document2 = _interopRequireDefault(_document); + +var _obj = _dereq_(93); + +var _trackTypes = _dereq_(77); + +var TRACK_TYPES = _interopRequireWildcard(_trackTypes); + +var _toTitleCase = _dereq_(96); + +var _toTitleCase2 = _interopRequireDefault(_toTitleCase); + +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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +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; } /** + * @file tech.js + */ + +/** + * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string + * that just contains the src url alone. + * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};` + * `var SourceString = 'http://example.com/some-video.mp4';` + * + * @typedef {Object|string} Tech~SourceObject + * + * @property {string} src + * The url to the source + * + * @property {string} type + * The mime type of the source + */ + +/** + * A function used by {@link Tech} to create a new {@link TextTrack}. + * + * @private + * + * @param {Tech} self + * An instance of the Tech class. + * + * @param {string} kind + * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) + * + * @param {string} [label] + * Label to identify the text track + * + * @param {string} [language] + * Two letter language abbreviation + * + * @param {Object} [options={}] + * An object with additional text track options + * + * @return {TextTrack} + * The text track that was created. + */ +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 TRACK_TYPES.ALL.text.TrackClass(options); + + tracks.addTrack(track); + + return track; +} + +/** + * This is the base class for media playback technology controllers, such as + * {@link Flash} and {@link HTML5} + * + * @extends Component + */ + +var Tech = function (_Component) { + _inherits(Tech, _Component); + + /** + * Create an instance of this Tech. + * + * @param {Object} [options] + * The key/value store of player options. + * + * @param {Component~ReadyCallback} ready + * Callback function to call when the `HTML5` Tech is ready. + */ + function Tech() { + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var ready = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {}; + + _classCallCheck(this, Tech); + + // we don't want the tech to report user activity automatically. + // This is done manually in addControlsListeners + options.reportTouchActivity = false; + + // keep track of whether the current source has played at all to + // implement a very limited played() + var _this = _possibleConstructorReturn(this, _Component.call(this, null, options, ready)); + + _this.hasStarted_ = false; + _this.on('playing', function () { + this.hasStarted_ = true; + }); + _this.on('loadstart', function () { + this.hasStarted_ = false; + }); + + TRACK_TYPES.ALL.names.forEach(function (name) { + var props = TRACK_TYPES.ALL[name]; + + if (options && options[props.getterName]) { + _this[props.privateName] = options[props.getterName]; + } + }); + + // 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(); + } + + ['Text', 'Audio', 'Video'].forEach(function (track) { + if (options['native' + track + 'Tracks'] === false) { + _this['featuresNative' + track + 'Tracks'] = false; + } + }); + + if (options.nativeCaptions === false || options.nativeTextTracks === false) { + _this.featuresNativeTextTracks = false; + } else if (options.nativeCaptions === true || options.nativeTextTracks === true) { + _this.featuresNativeTextTracks = true; + } + + if (!_this.featuresNativeTextTracks) { + _this.emulateTextTracks(); + } + + _this.autoRemoteTextTracks_ = new TRACK_TYPES.ALL.text.ListClass(); + + _this.initTrackListeners(); + + // Turn on component tap events only if not using native controls + if (!options.nativeControlsForTouch) { + _this.emitTapEvents(); + } + + if (_this.constructor) { + _this.name_ = _this.constructor.name || 'Unknown Tech'; + } + return _this; + } + + /* Fallbacks for unsupported event types + ================================================================================ */ + + /** + * Polyfill the `progress` event for browsers that don't support it natively. + * + * @see {@link Tech#trackProgress} + */ + + + 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 the polyfill for `progress` events that was created in + * {@link Tech#manualProgressOn} + */ + + + Tech.prototype.manualProgressOff = function manualProgressOff() { + this.manualProgress = false; + this.stopTrackingProgress(); + + this.off('durationchange', this.onDurationChange); + }; + + /** + * This is used to trigger a `progress` event when the buffered percent changes. It + * sets an interval function that will be called every 500 milliseconds to check if the + * buffer end percent has changed. + * + * > This function is called by {@link Tech#manualProgressOn} + * + * @param {EventTarget~Event} event + * The `ready` event that caused this to run. + * + * @listens Tech#ready + * @fires Tech#progress + */ + + + Tech.prototype.trackProgress = function trackProgress(event) { + 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) { + /** + * See {@link Player#progress} + * + * @event Tech#progress + * @type {EventTarget~Event} + */ + this.trigger('progress'); + } + + this.bufferedPercent_ = numBufferedPercent; + + if (numBufferedPercent === 1) { + this.stopTrackingProgress(); + } + }), 500); + }; + + /** + * Update our internal duration on a `durationchange` event by calling + * {@link Tech#duration}. + * + * @param {EventTarget~Event} event + * The `durationchange` event that caused this to run. + * + * @listens Tech#durationchange + */ + + + Tech.prototype.onDurationChange = function onDurationChange(event) { + this.duration_ = this.duration(); + }; + + /** + * Get and create a `TimeRange` object for buffering. + * + * @return {TimeRange} + * The time range object that was created. + */ + + + Tech.prototype.buffered = function buffered() { + return (0, _timeRanges.createTimeRange)(0, 0); + }; + + /** + * Get the percentage of the current video that is currently buffered. + * + * @return {number} + * A number from 0 to 1 that represents the decimal percentage of the + * video that is buffered. + * + */ + + + Tech.prototype.bufferedPercent = function bufferedPercent() { + return (0, _buffer.bufferedPercent)(this.buffered(), this.duration_); + }; + + /** + * Turn off the polyfill for `progress` events that was created in + * {@link Tech#manualProgressOn} + * Stop manually tracking progress events by clearing the interval that was set in + * {@link Tech#trackProgress}. + */ + + + Tech.prototype.stopTrackingProgress = function stopTrackingProgress() { + this.clearInterval(this.progressInterval); + }; + + /** + * Polyfill the `timeupdate` event for browsers that don't support it. + * + * @see {@link Tech#trackCurrentTime} + */ + + + Tech.prototype.manualTimeUpdatesOn = function manualTimeUpdatesOn() { + this.manualTimeUpdates = true; + + this.on('play', this.trackCurrentTime); + this.on('pause', this.stopTrackingCurrentTime); + }; + + /** + * Turn off the polyfill for `timeupdate` events that was created in + * {@link Tech#manualTimeUpdatesOn} + */ + + + Tech.prototype.manualTimeUpdatesOff = function manualTimeUpdatesOff() { + this.manualTimeUpdates = false; + this.stopTrackingCurrentTime(); + this.off('play', this.trackCurrentTime); + this.off('pause', this.stopTrackingCurrentTime); + }; + + /** + * Sets up an interval function to track current time and trigger `timeupdate` every + * 250 milliseconds. + * + * @listens Tech#play + * @triggers Tech#timeupdate + */ + + + Tech.prototype.trackCurrentTime = function trackCurrentTime() { + if (this.currentTimeInterval) { + this.stopTrackingCurrentTime(); + } + this.currentTimeInterval = this.setInterval(function () { + /** + * Triggered at an interval of 250ms to indicated that time is passing in the video. + * + * @event Tech#timeupdate + * @type {EventTarget~Event} + */ + this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); + + // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 + }, 250); + }; + + /** + * Stop the interval function created in {@link Tech#trackCurrentTime} so that the + * `timeupdate` event is no longer triggered. + * + * @listens {Tech#pause} + */ + + + 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 all event polyfills, clear the `Tech`s {@link AudioTrackList}, + * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech. + * + * @fires Component#dispose + */ + + + Tech.prototype.dispose = function dispose() { + + // clear out all tracks because we can't reuse them between techs + this.clearTracks(TRACK_TYPES.NORMAL.names); + + // 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 single `TrackList` or an array of `TrackLists` given their names. + * + * > Note: Techs without source handlers should call this between sources for `video` + * & `audio` tracks. You don't want to use them between tracks! + * + * @param {string[]|string} types + * TrackList names to clear, valid names are `video`, `audio`, and + * `text`. + */ + + + Tech.prototype.clearTracks = function clearTracks(types) { + var _this2 = this; + + types = [].concat(types); + // clear out all tracks because we can't reuse them between techs + types.forEach(function (type) { + var list = _this2[type + 'Tracks']() || []; + var i = list.length; + + while (i--) { + var track = list[i]; + + if (type === 'text') { + _this2.removeRemoteTextTrack(track); + } + list.removeTrack(track); + } + }); + }; + + /** + * Remove any TextTracks added via addRemoteTextTrack that are + * flagged for automatic garbage collection + */ + + + Tech.prototype.cleanupAutoTextTracks = function cleanupAutoTextTracks() { + var list = this.autoRemoteTextTracks_ || []; + var i = list.length; + + while (i--) { + var track = list[i]; + + this.removeRemoteTextTrack(track); + } + }; + + /** + * Reset the tech, which will removes all sources and reset the internal readyState. + * + * @abstract + */ + + + Tech.prototype.reset = function reset() {}; + + /** + * Get or set an error on the Tech. + * + * @param {MediaError} [err] + * Error to set on the Tech + * + * @return {MediaError|null} + * The current error object on the tech, or null if there isn't one. + */ + + + Tech.prototype.error = function error(err) { + if (err !== undefined) { + this.error_ = new _mediaError2['default'](err); + this.trigger('error'); + } + return this.error_; + }; + + /** + * Returns the `TimeRange`s that have been played through for the current source. + * + * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`. + * It only checks wether the source has played at all or not. + * + * @return {TimeRange} + * - A single time range if this video has played + * - An empty set of ranges if not. + */ + + + Tech.prototype.played = function played() { + if (this.hasStarted_) { + return (0, _timeRanges.createTimeRange)(0, 0); + } + return (0, _timeRanges.createTimeRange)(); + }; + + /** + * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was + * previously called. + * + * @fires Tech#timeupdate + */ + + + Tech.prototype.setCurrentTime = function setCurrentTime() { + // improve the accuracy of manual timeupdates + if (this.manualTimeUpdates) { + /** + * A manual `timeupdate` event. + * + * @event Tech#timeupdate + * @type {EventTarget~Event} + */ + this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); + } + }; + + /** + * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and + * {@link TextTrackList} events. + * + * This adds {@link EventTarget~EventListeners} for `addtrack`, and `removetrack`. + * + * @fires Tech#audiotrackchange + * @fires Tech#videotrackchange + * @fires Tech#texttrackchange + */ + + + Tech.prototype.initTrackListeners = function initTrackListeners() { + var _this3 = this; + + /** + * Triggered when tracks are added or removed on the Tech {@link AudioTrackList} + * + * @event Tech#audiotrackchange + * @type {EventTarget~Event} + */ + + /** + * Triggered when tracks are added or removed on the Tech {@link VideoTrackList} + * + * @event Tech#videotrackchange + * @type {EventTarget~Event} + */ + + /** + * Triggered when tracks are added or removed on the Tech {@link TextTrackList} + * + * @event Tech#texttrackchange + * @type {EventTarget~Event} + */ + TRACK_TYPES.NORMAL.names.forEach(function (name) { + var props = TRACK_TYPES.NORMAL[name]; + var trackListChanges = function trackListChanges() { + _this3.trigger(name + 'trackchange'); + }; + + var tracks = _this3[props.getterName](); + + tracks.addEventListener('removetrack', trackListChanges); + tracks.addEventListener('addtrack', trackListChanges); + + _this3.on('dispose', function () { + tracks.removeEventListener('removetrack', trackListChanges); + tracks.removeEventListener('addtrack', trackListChanges); + }); + }); + }; + + /** + * Emulate TextTracks using vtt.js if necessary + * + * @fires Tech#vttjsloaded + * @fires Tech#vttjserror + */ + + + Tech.prototype.addWebVttScript_ = function addWebVttScript_() { + var _this4 = this; + + if (_window2['default'].WebVTT) { + return; + } + + // Initially, Tech.el_ is a child of a dummy-div wait until the Component system + // signals that the Tech is ready at which point Tech.el_ is part of the DOM + // before inserting the WebVTT script + if (_document2['default'].body.contains(this.el())) { + var vtt = _dereq_(104); + + // load via require if available and vtt.js script location was not passed in + // as an option. novtt builds will turn the above require call into an empty object + // which will cause this if check to always fail. + if (!this.options_['vtt.js'] && (0, _obj.isPlain)(vtt) && Object.keys(vtt).length > 0) { + this.trigger('vttjsloaded'); + return; + } + + // load vtt.js via the script location option or the cdn of no location was + // passed in + var script = _document2['default'].createElement('script'); + + script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.12.3/vtt.min.js'; + script.onload = function () { + /** + * Fired when vtt.js is loaded. + * + * @event Tech#vttjsloaded + * @type {EventTarget~Event} + */ + _this4.trigger('vttjsloaded'); + }; + script.onerror = function () { + /** + * Fired when vtt.js was not loaded due to an error + * + * @event Tech#vttjsloaded + * @type {EventTarget~Event} + */ + _this4.trigger('vttjserror'); + }; + this.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 + _window2['default'].WebVTT = true; + this.el().parentNode.appendChild(script); + } else { + this.ready(this.addWebVttScript_); + } + }; + + /** + * Emulate texttracks + * + */ + + + Tech.prototype.emulateTextTracks = function emulateTextTracks() { + var _this5 = this; + + var tracks = this.textTracks(); + var remoteTracks = this.remoteTextTracks(); + var handleAddTrack = function handleAddTrack(e) { + return tracks.addTrack(e.track); + }; + var handleRemoveTrack = function handleRemoveTrack(e) { + return tracks.removeTrack(e.track); + }; + + remoteTracks.on('addtrack', handleAddTrack); + remoteTracks.on('removetrack', handleRemoveTrack); + + this.addWebVttScript_(); + + var updateDisplay = function updateDisplay() { + return _this5.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); + tracks.addEventListener('addtrack', textTracksChanges); + tracks.addEventListener('removetrack', textTracksChanges); + + this.on('dispose', function () { + remoteTracks.off('addtrack', handleAddTrack); + remoteTracks.off('removetrack', handleRemoveTrack); + tracks.removeEventListener('change', textTracksChanges); + tracks.removeEventListener('addtrack', textTracksChanges); + tracks.removeEventListener('removetrack', textTracksChanges); + + for (var i = 0; i < tracks.length; i++) { + var track = tracks[i]; + + track.removeEventListener('cuechange', updateDisplay); + } + }); + }; + + /** + * Create and returns a remote {@link TextTrack} object. + * + * @param {string} kind + * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) + * + * @param {string} [label] + * Label to identify the text track + * + * @param {string} [language] + * Two letter language abbreviation + * + * @return {TextTrack} + * The TextTrack that gets created. + */ + + + 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); + }; + + /** + * Create an emulated TextTrack for use by addRemoteTextTrack + * + * This is intended to be overridden by classes that inherit from + * Tech in order to create native or custom TextTracks. + * + * @param {Object} options + * The object should contain the options to initialize the TextTrack with. + * + * @param {string} [options.kind] + * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata). + * + * @param {string} [options.label]. + * Label to identify the text track + * + * @param {string} [options.language] + * Two letter language abbreviation. + * + * @return {HTMLTrackElement} + * The track element that gets created. + */ + + + Tech.prototype.createRemoteTextTrack = function createRemoteTextTrack(options) { + var track = (0, _mergeOptions2['default'])(options, { + tech: this + }); + + return new TRACK_TYPES.REMOTE.remoteTextEl.TrackClass(track); + }; + + /** + * Creates a remote text track object and returns an html track element. + * + * > Note: This can be an emulated {@link HTMLTrackElement} or a native one. + * + * @param {Object} options + * See {@link Tech#createRemoteTextTrack} for more detailed properties. + * + * @param {boolean} [manualCleanup=true] + * - When false: the TextTrack will be automatically removed from the video + * element whenever the source changes + * - When True: The TextTrack will have to be cleaned up manually + * + * @return {HTMLTrackElement} + * An Html Track Element. + * + * @deprecated The default functionality for this function will be equivalent + * to "manualCleanup=false" in the future. The manualCleanup parameter will + * also be removed. + */ + + + Tech.prototype.addRemoteTextTrack = function addRemoteTextTrack() { + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var manualCleanup = arguments[1]; + + var htmlTrackElement = this.createRemoteTextTrack(options); + + if (manualCleanup !== true && manualCleanup !== false) { + // deprecation warning + _log2['default'].warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js'); + manualCleanup = true; + } + + // store HTMLTrackElement and TextTrack to remote list + this.remoteTextTrackEls().addTrackElement_(htmlTrackElement); + this.remoteTextTracks().addTrack(htmlTrackElement.track); + + if (manualCleanup !== true) { + // create the TextTrackList if it doesn't exist + this.autoRemoteTextTracks_.addTrack(htmlTrackElement.track); + } + + return htmlTrackElement; + }; + + /** + * Remove a remote text track from the remote `TextTrackList`. + * + * @param {TextTrack} track + * `TextTrack` to remove from the `TextTrackList` + */ + + + Tech.prototype.removeRemoteTextTrack = function removeRemoteTextTrack(track) { + var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); + + // remove HTMLTrackElement and TextTrack from remote list + this.remoteTextTrackEls().removeTrackElement_(trackElement); + this.remoteTextTracks().removeTrack(track); + this.autoRemoteTextTracks_.removeTrack(track); + }; + + /** + * Gets available media playback quality metrics as specified by the W3C's Media + * Playback Quality API. + * + * @see [Spec]{@link https://wicg.github.io/media-playback-quality} + * + * @return {Object} + * An object with supported media playback quality metrics + * + * @abstract + */ + + + Tech.prototype.getVideoPlaybackQuality = function getVideoPlaybackQuality() { + return {}; + }; + + /** + * A method to set a poster from a `Tech`. + * + * @abstract + */ + + + Tech.prototype.setPoster = function setPoster() {}; + + /** + * A method to check for the presence of the 'playsinine'
  • ` - * - * @extends ClickableComponent + * Calls a getter on the tech first, through each middleware + * from right to left to the player. */ -var MenuItem = function (_ClickableComponent) { - _inherits(MenuItem, _ClickableComponent); - - /** - * Creates an instance of the this class. - * - * @param {Player} player - * The `Player` that this class should be attached to. - * - * @param {Object} [options={}] - * The key/value store of player options. - * - */ - function MenuItem(player, options) { - _classCallCheck(this, MenuItem); +function get$1(middleware, tech, method) { + return middleware.reduceRight(middlewareIterator(method), tech[method]()); +} - var _this = _possibleConstructorReturn(this, _ClickableComponent.call(this, player, options)); +/** + * Takes the argument given to the player and calls the setter method on each + * middlware from left to right to the tech. + */ +function set$1(middleware, tech, method, arg) { + return tech[method](middleware.reduce(middlewareIterator(method), arg)); +} - _this.selectable = options.selectable; +/** + * Takes the argument given to the player and calls the `call` version of the method + * on each middleware from left to right. + * Then, call the passed in method on the tech and return the result unchanged + * back to the player, through middleware, this time from right to left. + */ +function mediate(middleware, tech, method) { + var arg = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; - _this.selected(options.selected); + var callMethod = 'call' + toTitleCase(method); + var middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg); + var terminated = middlewareValue === TERMINATOR; + var returnValue = terminated ? null : tech[method](middlewareValue); - 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'); - } - return _this; - } + executeRight(middleware, method, returnValue, terminated); - /** - * Create the `MenuItem's DOM element - * - * @param {string} [type=li] - * Element's node type, not actually used, always set to `li`. - * - * @param {Object} [props={}] - * An object of properties that should be set on the element - * - * @param {Object} [attrs={}] - * An object of attributes that should be set on the element - * - * @return {Element} - * The element that gets created. - */ + return returnValue; +} +var allowedGetters = { + buffered: 1, + currentTime: 1, + duration: 1, + seekable: 1, + played: 1, + paused: 1 +}; - MenuItem.prototype.createEl = function createEl(type, props, attrs) { - // The control is textual, not just an icon - this.nonIconControl = true; +var allowedSetters = { + setCurrentTime: 1 +}; - return _ClickableComponent.prototype.createEl.call(this, 'li', (0, _obj.assign)({ - className: 'vjs-menu-item', - innerHTML: '' + this.localize(this.options_.label) + '', - tabIndex: -1 - }, props), attrs); - }; +var allowedMediators = { + play: 1, + pause: 1 +}; - /** - * Any click on a `MenuItem` puts int into the selected state. - * See {@link ClickableComponent#handleClick} for instances where this is called. - * - * @param {EventTarget~Event} event - * The `keydown`, `tap`, or `click` event that caused this function to be - * called. - * - * @listens tap - * @listens click - */ +function middlewareIterator(method) { + return function (value, mw) { + // if the previous middleware terminated, pass along the termination + if (value === TERMINATOR) { + return TERMINATOR; + } + if (mw[method]) { + return mw[method](value); + } - MenuItem.prototype.handleClick = function handleClick(event) { - this.selected(true); + return value; }; +} - /** - * Set the state for this menu item as selected or not. - * - * @param {boolean} selected - * if the menu item is selected or not - */ - +function executeRight(mws, method, value, terminated) { + for (var i = mws.length - 1; i >= 0; i--) { + var mw = mws[i]; - 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(' '); - } + if (mw[method]) { + mw[method](terminated, value); } - }; - - return MenuItem; -}(_clickableComponent2['default']); - -_component2['default'].registerComponent('MenuItem', MenuItem); -exports['default'] = MenuItem; + } +} -},{"3":3,"5":5,"93":93}],52:[function(_dereq_,module,exports){ -'use strict'; +function clearCacheForPlayer(player) { + middlewareInstances[player.id()] = null; +} -exports.__esModule = true; +/** + * { + * [playerId]: [[mwFactory, mwInstance], ...] + * } + */ +function getOrCreateFactory(player, mwFactory) { + var mws = middlewareInstances[player.id()]; + var mw = null; -var _component = _dereq_(5); + if (mws === undefined || mws === null) { + mw = mwFactory(player); + middlewareInstances[player.id()] = [[mwFactory, mw]]; + return mw; + } -var _component2 = _interopRequireDefault(_component); + for (var i = 0; i < mws.length; i++) { + var _mws$i = mws[i], + mwf = _mws$i[0], + mwi = _mws$i[1]; -var _dom = _dereq_(85); -var Dom = _interopRequireWildcard(_dom); + if (mwf !== mwFactory) { + continue; + } -var _fn = _dereq_(88); + mw = mwi; + } -var Fn = _interopRequireWildcard(_fn); + if (mw === null) { + mw = mwFactory(player); + mws.push([mwFactory, mw]); + } -var _events = _dereq_(86); + return mw; +} -var Events = _interopRequireWildcard(_events); +function setSourceHelper() { + var src = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var middleware = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; + var next = arguments[2]; + var player = arguments[3]; + var acc = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : []; + var lastRun = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false; + var mwFactory = middleware[0], + mwrest = middleware.slice(1); -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 mwFactory is a string, then we're at a fork in the road -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + if (typeof mwFactory === 'string') { + setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun); -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + // if we have an mwFactory, call it with the player to get the mw, + // then call the mw's setSource method + } else if (mwFactory) { + var mw = getOrCreateFactory(player, mwFactory); -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + mw.setSource(assign({}, src), function (err, _src) { -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; } /** - * @file menu.js - */ + // something happened, try the next middleware on the current level + // make sure to use the old src + if (err) { + return setSourceHelper(src, mwrest, next, player, acc, lastRun); + } + // we've succeeded, now we need to go deeper + acc.push(mw); + + // if it's the same type, continue down the current chain + // otherwise, we want to go down the new chain + setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun); + }); + } else if (mwrest.length) { + setSourceHelper(src, mwrest, next, player, acc, lastRun); + } else if (lastRun) { + next(src, acc); + } else { + setSourceHelper(src, middlewares['*'], next, player, acc, true); + } +} /** - * The Menu component is used to build popup menus, including subtitle and - * captions selection menus. + * @module filter-source + */ +/** + * Filter out single bad source objects or multiple source objects in an + * array. Also flattens nested source object arrays into a 1 dimensional + * array of source objects. + * + * @param {Tech~SourceObject|Tech~SourceObject[]} src + * The src object to filter + * + * @return {Tech~SourceObject[]} + * An array of sourceobjects containing only valid sources + * + * @private + */ +var filterSource = function filterSource(src) { + // traverse array + if (Array.isArray(src)) { + var newsrc = []; + + src.forEach(function (srcobj) { + srcobj = filterSource(srcobj); + + if (Array.isArray(srcobj)) { + newsrc = newsrc.concat(srcobj); + } else if (isObject(srcobj)) { + newsrc.push(srcobj); + } + }); + + src = newsrc; + } else if (typeof src === 'string' && src.trim()) { + // convert string into object + src = [{ src: src }]; + } else if (isObject(src) && typeof src.src === 'string' && src.src && src.src.trim()) { + // src is already valid + src = [src]; + } else { + // invalid source, turn it into an empty array + src = []; + } + + return src; +}; + +/** + * @file loader.js + */ +/** + * The `MediaLoader` is the `Component` that decides which playback technology to load + * when a player is initialized. * * @extends Component */ -var Menu = function (_Component) { - _inherits(Menu, _Component); + +var MediaLoader = function (_Component) { + inherits(MediaLoader, _Component); /** * Create an instance of this class. * * @param {Player} player - * the player that this component should attach to + * The `Player` that this class should attach to. * * @param {Object} [options] - * Object of option names and values + * The key/value stroe of player options. * + * @param {Component~ReadyCallback} [ready] + * The function that is run when this component is ready. */ - function Menu(player, options) { - _classCallCheck(this, Menu); + function MediaLoader(player, options, ready) { + classCallCheck(this, MediaLoader); - var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); + // MediaLoader has no element + var options_ = mergeOptions({ createEl: false }, options); - if (options) { - _this.menuButton_ = options.menuButton; - } + // If there are no sources when the player is initialized, + // load the first supported playback technology. - _this.focusedChild_ = -1; + var _this = possibleConstructorReturn(this, _Component.call(this, player, options_, ready)); - _this.on('keydown', _this.handleKeyPress); + if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) { + for (var i = 0, j = options.playerOptions.techOrder; i < j.length; i++) { + var techName = toTitleCase(j[i]); + var tech = Tech.getTech(techName); + + // Support old behavior of techs being registered as components. + // Remove once that deprecated behavior is removed. + if (!techName) { + tech = Component.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 _this; } + return MediaLoader; +}(Component); + +Component.registerComponent('MediaLoader', MediaLoader); + +/** + * @file button.js + */ +/** + * Clickable Component which is clickable or keyboard actionable, + * but is not a native HTML button. + * + * @extends Component + */ + +var ClickableComponent = function (_Component) { + inherits(ClickableComponent, _Component); + /** - * Add a {@link MenuItem} to the menu. + * Creates an instance of this class. * - * @param {Object|string} component - * The name or instance of the `MenuItem` to add. + * @param {Player} player + * The `Player` that this class should be attached to. * + * @param {Object} [options] + * The key/value store of player options. */ + function ClickableComponent(player, options) { + classCallCheck(this, ClickableComponent); + var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); - Menu.prototype.addItem = function addItem(component) { - this.addChild(component); - component.on('click', Fn.bind(this, function (event) { - // Unpress the associated MenuButton, and move focus back to it - if (this.menuButton_) { - this.menuButton_.unpressButton(); + _this.emitTapEvents(); - // don't focus menu button if item is a caption settings item - // because focus will move elsewhere and it logs an error on IE8 - if (component.name() !== 'CaptionSettingsMenuItem') { - this.menuButton_.focus(); - } - } - })); - }; + _this.enable(); + return _this; + } /** - * Create the `Menu`s DOM element. + * Create the `Component`s DOM element. + * + * @param {string} [tag=div] + * The element's node type. + * + * @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} - * the element that was created + * The element that gets created. */ - Menu.prototype.createEl = function createEl() { - var contentElType = this.options_.contentElType || 'ul'; + ClickableComponent.prototype.createEl = function createEl$$1() { + var tag = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'div'; + var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - this.contentEl_ = Dom.createEl(contentElType, { - className: 'vjs-menu-content' - }); + props = assign({ + innerHTML: '', + className: this.buildCSSClass(), + tabIndex: 0 + }, props); - this.contentEl_.setAttribute('role', 'menu'); + if (tag === 'button') { + log$1.error('Creating a ClickableComponent with an HTML element of ' + tag + ' is not supported; use a Button instead.'); + } - var el = _Component.prototype.createEl.call(this, 'div', { - append: this.contentEl_, - className: 'vjs-menu' - }); + // Add ARIA attributes for clickable element which is not a native HTML button + attributes = assign({ + 'role': 'button', - el.appendChild(this.contentEl_); + // let the screen reader user know that the text of the element may change + 'aria-live': 'polite' + }, attributes); - // 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(); - }); + this.tabIndex_ = props.tabIndex; + + var el = _Component.prototype.createEl.call(this, tag, props, attributes); + + this.createControlTextEl(el); return el; }; + ClickableComponent.prototype.dispose = function dispose() { + // remove controlTextEl_ on dipose + this.controlTextEl_ = null; + + _Component.prototype.dispose.call(this); + }; + /** - * Handle a `keydown` event on this menu. This listener is added in the constructor. + * Create a control text element on this `Component` * - * @param {EventTarget~Event} event - * A `keydown` event that happened on the menu. + * @param {Element} [el] + * Parent element for the control text. * - * @listens keydown + * @return {Element} + * The control text element that gets created. */ - Menu.prototype.handleKeyPress = function handleKeyPress(event) { - // Left and Down Arrows - if (event.which === 37 || event.which === 40) { - event.preventDefault(); - this.stepForward(); + ClickableComponent.prototype.createControlTextEl = function createControlTextEl(el) { + this.controlTextEl_ = createEl('span', { + className: 'vjs-control-text' + }); - // Up and Right Arrows - } else if (event.which === 38 || event.which === 39) { - event.preventDefault(); - this.stepBack(); + if (el) { + el.appendChild(this.controlTextEl_); } + + this.controlText(this.controlText_, el); + + return this.controlTextEl_; }; /** - * Move to next (lower) menu item for keyboard users. + * Get or set the localize text to use for the controls on the `Component`. + * + * @param {string} [text] + * Control text for element. + * + * @param {Element} [el=this.el()] + * Element to set the title on. + * + * @return {string} + * - The control text when getting */ - Menu.prototype.stepForward = function stepForward() { - var stepChild = 0; + ClickableComponent.prototype.controlText = function controlText(text) { + var el = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.el(); - if (this.focusedChild_ !== undefined) { - stepChild = this.focusedChild_ + 1; + if (text === undefined) { + return this.controlText_ || 'Need Text'; } - this.focus(stepChild); - }; - - /** - * Move to previous (higher) menu item for keyboard users. - */ + var localizedText = this.localize(text); - Menu.prototype.stepBack = function stepBack() { - var stepChild = 0; - - if (this.focusedChild_ !== undefined) { - stepChild = this.focusedChild_ - 1; + this.controlText_ = text; + textContent(this.controlTextEl_, localizedText); + if (!this.nonIconControl) { + // Set title attribute if only an icon is shown + el.setAttribute('title', localizedText); } - this.focus(stepChild); }; /** - * Set focus on a {@link MenuItem} in the `Menu`. + * Builds the default DOM `className`. * - * @param {Object|string} [item=0] - * Index of child item set focus on. + * @return {string} + * The DOM `className` for this object. */ - Menu.prototype.focus = function focus() { - var item = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; + ClickableComponent.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-control vjs-button ' + _Component.prototype.buildCSSClass.call(this); + }; - var children = this.children().slice(); - var haveTitle = children.length && children[0].className && /vjs-menu-title/.test(children[0].className); + /** + * Enable this `Component`s element. + */ - if (haveTitle) { - children.shift(); - } - if (children.length > 0) { - if (item < 0) { - item = 0; - } else if (item >= children.length) { - item = children.length - 1; + ClickableComponent.prototype.enable = function enable() { + if (!this.enabled_) { + this.enabled_ = true; + this.removeClass('vjs-disabled'); + this.el_.setAttribute('aria-disabled', 'false'); + if (typeof this.tabIndex_ !== 'undefined') { + this.el_.setAttribute('tabIndex', this.tabIndex_); } - - this.focusedChild_ = item; - - children[item].el_.focus(); + this.on(['tap', 'click'], this.handleClick); + this.on('focus', this.handleFocus); + this.on('blur', this.handleBlur); } }; - return Menu; -}(_component2['default']); - -_component2['default'].registerComponent('Menu', Menu); -exports['default'] = Menu; + /** + * Disable this `Component`s element. + */ -},{"5":5,"85":85,"86":86,"88":88}],53:[function(_dereq_,module,exports){ -'use strict'; -exports.__esModule = true; -exports.isEvented = undefined; + ClickableComponent.prototype.disable = function disable() { + this.enabled_ = false; + this.addClass('vjs-disabled'); + this.el_.setAttribute('aria-disabled', 'true'); + if (typeof this.tabIndex_ !== 'undefined') { + this.el_.removeAttribute('tabIndex'); + } + this.off(['tap', 'click'], this.handleClick); + this.off('focus', this.handleFocus); + this.off('blur', this.handleBlur); + }; -var _dom = _dereq_(85); + /** + * This gets called when a `ClickableComponent` gets: + * - Clicked (via the `click` event, listening starts in the constructor) + * - Tapped (via the `tap` event, listening starts in the constructor) + * - The following things happen in order: + * 1. {@link ClickableComponent#handleFocus} is called via a `focus` event on the + * `ClickableComponent`. + * 2. {@link ClickableComponent#handleFocus} adds a listener for `keydown` on using + * {@link ClickableComponent#handleKeyPress}. + * 3. `ClickableComponent` has not had a `blur` event (`blur` means that focus was lost). The user presses + * the space or enter key. + * 4. {@link ClickableComponent#handleKeyPress} calls this function with the `keydown` + * event as a parameter. + * + * @param {EventTarget~Event} event + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click + * @abstract + */ -var Dom = _interopRequireWildcard(_dom); -var _events = _dereq_(86); + ClickableComponent.prototype.handleClick = function handleClick(event) {}; -var Events = _interopRequireWildcard(_events); + /** + * This gets called when a `ClickableComponent` gains focus via a `focus` event. + * Turns on listening for `keydown` events. When they happen it + * calls `this.handleKeyPress`. + * + * @param {EventTarget~Event} event + * The `focus` event that caused this function to be called. + * + * @listens focus + */ -var _fn = _dereq_(88); -var Fn = _interopRequireWildcard(_fn); + ClickableComponent.prototype.handleFocus = function handleFocus(event) { + on(document_1, 'keydown', bind(this, this.handleKeyPress)); + }; -var _obj = _dereq_(93); + /** + * Called when this ClickableComponent has focus and a key gets pressed down. By + * default it will call `this.handleClick` when the key is space or enter. + * + * @param {EventTarget~Event} event + * The `keydown` event that caused this function to be called. + * + * @listens keydown + */ -var Obj = _interopRequireWildcard(_obj); -var _eventTarget = _dereq_(45); + ClickableComponent.prototype.handleKeyPress = function handleKeyPress(event) { -var _eventTarget2 = _interopRequireDefault(_eventTarget); + // Support Space (32) or Enter (13) key operation to fire a click event + if (event.which === 32 || event.which === 13) { + event.preventDefault(); + this.trigger('click'); + } else if (_Component.prototype.handleKeyPress) { -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + // Pass keypress handling up for unsupported keys + _Component.prototype.handleKeyPress.call(this, event); + } + }; -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; } } + /** + * Called when a `ClickableComponent` loses focus. Turns off the listener for + * `keydown` events. Which Stops `this.handleKeyPress` from getting called. + * + * @param {EventTarget~Event} event + * The `blur` event that caused this function to be called. + * + * @listens blur + */ -/** - * Returns whether or not an object has had the evented mixin applied. - * - * @param {Object} object - * An object to test. - * - * @return {boolean} - * Whether or not the object appears to be evented. - */ -var isEvented = function isEvented(object) { - return object instanceof _eventTarget2['default'] || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(function (k) { - return typeof object[k] === 'function'; - }); -}; -/** - * Whether a value is a valid event type - non-empty string or array. - * - * @private - * @param {string|Array} type - * The type value to test. - * - * @return {boolean} - * Whether or not the type is a valid event type. - */ -/** - * @file mixins/evented.js - * @module evented - */ -var isValidEventType = function isValidEventType(type) { - return ( - // The regex here verifies that the `type` contains at least one non- - // whitespace character. - typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length - ); -}; + ClickableComponent.prototype.handleBlur = function handleBlur(event) { + off(document_1, 'keydown', bind(this, this.handleKeyPress)); + }; -/** - * Validates a value to determine if it is a valid event target. Throws if not. - * - * @private - * @throws {Error} - * If the target does not appear to be a valid event target. - * - * @param {Object} target - * The object to test. - */ -var validateTarget = function validateTarget(target) { - if (!target.nodeName && !isEvented(target)) { - throw new Error('Invalid target; must be a DOM node or evented object.'); - } -}; + return ClickableComponent; +}(Component); -/** - * Validates a value to determine if it is a valid event target. Throws if not. - * - * @private - * @throws {Error} - * If the type does not appear to be a valid event type. - * - * @param {string|Array} type - * The type to test. - */ -var validateEventType = function validateEventType(type) { - if (!isValidEventType(type)) { - throw new Error('Invalid event type; must be a non-empty string or array.'); - } -}; +Component.registerComponent('ClickableComponent', ClickableComponent); /** - * Validates a value to determine if it is a valid listener. Throws if not. - * - * @private - * @throws {Error} - * If the listener is not a function. - * - * @param {Function} listener - * The listener to test. + * @file poster-image.js */ -var validateListener = function validateListener(listener) { - if (typeof listener !== 'function') { - throw new Error('Invalid listener; must be a function.'); - } -}; - /** - * Takes an array of arguments given to `on()` or `one()`, validates them, and - * normalizes them into an object. - * - * @private - * @param {Object} self - * The evented object on which `on()` or `one()` was called. This - * object will be bound as the `this` value for the listener. - * - * @param {Array} args - * An array of arguments passed to `on()` or `one()`. + * A `ClickableComponent` that handles showing the poster image for the player. * - * @return {Object} - * An object containing useful values for `on()` or `one()` calls. + * @extends ClickableComponent */ -var normalizeListenArgs = function normalizeListenArgs(self, args) { - // If the number of arguments is less than 3, the target is always the - // evented object itself. - var isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_; - var target = void 0; - var type = void 0; - var listener = void 0; +var PosterImage = function (_ClickableComponent) { + inherits(PosterImage, _ClickableComponent); - if (isTargetingSelf) { - target = self.eventBusEl_; + /** + * Create an instance of this class. + * + * @param {Player} player + * The `Player` that this class should attach to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function PosterImage(player, options) { + classCallCheck(this, PosterImage); - // Deal with cases where we got 3 arguments, but we are still listening to - // the evented object itself. - if (args.length >= 3) { - args.shift(); - } + var _this = possibleConstructorReturn(this, _ClickableComponent.call(this, player, options)); - type = args[0]; - listener = args[1]; - } else { - target = args[0]; - type = args[1]; - listener = args[2]; + _this.update(); + player.on('posterchange', bind(_this, _this.update)); + return _this; } - validateTarget(target); - validateEventType(type); - validateListener(listener); + /** + * Clean up and dispose of the `PosterImage`. + */ - listener = Fn.bind(self, listener); - return { isTargetingSelf: isTargetingSelf, target: target, type: type, listener: listener }; -}; - -/** - * Adds the listener to the event type(s) on the target, normalizing for - * the type of target. - * - * @private - * @param {Element|Object} target - * A DOM node or evented object. - * - * @param {string} method - * The event binding method to use ("on" or "one"). - * - * @param {string|Array} type - * One or more event type(s). - * - * @param {Function} listener - * A listener function. - */ -var listen = function listen(target, method, type, listener) { - validateTarget(target); - - if (target.nodeName) { - Events[method](target, type, listener); - } else { - target[method](type, listener); - } -}; - -/** - * Contains methods that provide event capabilites to an object which is passed - * to {@link module:evented|evented}. - * - * @mixin EventedMixin - */ -var EventedMixin = { + PosterImage.prototype.dispose = function dispose() { + this.player().off('posterchange', this.update); + _ClickableComponent.prototype.dispose.call(this); + }; /** - * Add a listener to an event (or events) on this object or another evented - * object. - * - * @param {string|Array|Element|Object} targetOrType - * If this is a string or array, it represents the event type(s) - * that will trigger the listener. - * - * Another evented object can be passed here instead, which will - * cause the listener to listen for events on _that_ object. - * - * In either case, the listener's `this` value will be bound to - * this object. - * - * @param {string|Array|Function} typeOrListener - * If the first argument was a string or array, this should be the - * listener function. Otherwise, this is a string or array of event - * type(s). + * Create the `PosterImage`s DOM element. * - * @param {Function} [listener] - * If the first argument was another evented object, this will be - * the listener function. + * @return {Element} + * The element that gets created. */ - on: function on() { - var _this = this; - - for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { - args[_key] = arguments[_key]; - } - - var _normalizeListenArgs = normalizeListenArgs(this, args), - isTargetingSelf = _normalizeListenArgs.isTargetingSelf, - target = _normalizeListenArgs.target, - type = _normalizeListenArgs.type, - listener = _normalizeListenArgs.listener; - - listen(target, 'on', type, listener); - - // If this object is listening to another evented object. - if (!isTargetingSelf) { - // If this object is disposed, remove the listener. - var removeListenerOnDispose = function removeListenerOnDispose() { - return _this.off(target, type, listener); - }; - - // Use the same function ID as the listener so we can remove it later it - // using the ID of the original listener. - removeListenerOnDispose.guid = listener.guid; - // Add a listener to the target's dispose event as well. This ensures - // that if the target is disposed BEFORE this object, we remove the - // removal listener that was just added. Otherwise, we create a memory leak. - var removeRemoverOnTargetDispose = function removeRemoverOnTargetDispose() { - return _this.off('dispose', removeListenerOnDispose); - }; + PosterImage.prototype.createEl = function createEl$$1() { + var el = createEl('div', { + className: 'vjs-poster', - // Use the same function ID as the listener so we can remove it later - // it using the ID of the original listener. - removeRemoverOnTargetDispose.guid = listener.guid; + // Don't want poster to be tabbable. + tabIndex: -1 + }); - listen(this, 'on', 'dispose', removeListenerOnDispose); - listen(target, 'on', 'dispose', removeRemoverOnTargetDispose); + // 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 (!BACKGROUND_SIZE_SUPPORTED) { + this.fallbackImg_ = createEl('img'); + el.appendChild(this.fallbackImg_); } - }, + return el; + }; /** - * Add a listener to an event (or events) on this object or another evented - * object. The listener will only be called once and then removed. - * - * @param {string|Array|Element|Object} targetOrType - * If this is a string or array, it represents the event type(s) - * that will trigger the listener. - * - * Another evented object can be passed here instead, which will - * cause the listener to listen for events on _that_ object. - * - * In either case, the listener's `this` value will be bound to - * this object. + * An {@link EventTarget~EventListener} for {@link Player#posterchange} events. * - * @param {string|Array|Function} typeOrListener - * If the first argument was a string or array, this should be the - * listener function. Otherwise, this is a string or array of event - * type(s). + * @listens Player#posterchange * - * @param {Function} [listener] - * If the first argument was another evented object, this will be - * the listener function. + * @param {EventTarget~Event} [event] + * The `Player#posterchange` event that triggered this function. */ - one: function one() { - var _this2 = this; - - for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { - args[_key2] = arguments[_key2]; - } - - var _normalizeListenArgs2 = normalizeListenArgs(this, args), - isTargetingSelf = _normalizeListenArgs2.isTargetingSelf, - target = _normalizeListenArgs2.target, - type = _normalizeListenArgs2.type, - listener = _normalizeListenArgs2.listener; - // Targeting this evented object. + PosterImage.prototype.update = function update(event) { + var url = this.player().poster(); - if (isTargetingSelf) { - listen(target, 'one', type, listener); + this.setSrc(url); - // Targeting another evented object. + // 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 { - var wrapper = function wrapper() { - for (var _len3 = arguments.length, largs = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { - largs[_key3] = arguments[_key3]; - } - - _this2.off(target, type, wrapper); - listener.apply(null, largs); - }; - - // Use the same function ID as the listener so we can remove it later - // it using the ID of the original listener. - wrapper.guid = listener.guid; - listen(target, 'one', type, wrapper); + this.hide(); } - }, - + }; /** - * Removes listener(s) from event(s) on an evented object. - * - * @param {string|Array|Element|Object} [targetOrType] - * If this is a string or array, it represents the event type(s). - * - * Another evented object can be passed here instead, in which case - * ALL 3 arguments are _required_. - * - * @param {string|Array|Function} [typeOrListener] - * If the first argument was a string or array, this may be the - * listener function. Otherwise, this is a string or array of event - * type(s). + * Set the source of the `PosterImage` depending on the display method. * - * @param {Function} [listener] - * If the first argument was another evented object, this will be - * the listener function; otherwise, _all_ listeners bound to the - * event type(s) will be removed. + * @param {string} url + * The URL to the source for the `PosterImage`. */ - off: function off(targetOrType, typeOrListener, listener) { - // Targeting this evented object. - if (!targetOrType || isValidEventType(targetOrType)) { - Events.off(this.eventBusEl_, targetOrType, typeOrListener); - // Targeting another evented object. + PosterImage.prototype.setSrc = function setSrc(url) { + if (this.fallbackImg_) { + this.fallbackImg_.src = url; } else { - var target = targetOrType; - var type = typeOrListener; - - // Fail fast and in a meaningful way! - validateTarget(target); - validateEventType(type); - validateListener(listener); - - // Ensure there's at least a guid, even if the function hasn't been used - listener = Fn.bind(this, listener); - - // Remove the dispose listener on this evented object, which was given - // the same guid as the event listener in on(). - this.off('dispose', listener); + var backgroundImage = ''; - if (target.nodeName) { - Events.off(target, type, listener); - Events.off(target, 'dispose', listener); - } else if (isEvented(target)) { - target.off(type, listener); - target.off('dispose', listener); + // 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; + } + }; /** - * Fire an event on this evented object, causing its listeners to be called. - * - * @param {string|Object} event - * An event type or an object with a type property. + * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See + * {@link ClickableComponent#handleClick} for instances where this will be triggered. * - * @param {Object} [hash] - * An additional object to pass along to listeners. + * @listens tap + * @listens click + * @listens keydown * - * @returns {boolean} - * Whether or not the default behavior was prevented. + * @param {EventTarget~Event} event + + The `click`, `tap` or `keydown` event that caused this function to be called. */ - trigger: function trigger(event, hash) { - return Events.trigger(this.eventBusEl_, event, hash); - } -}; - -/** - * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object. - * - * @param {Object} target - * The object to which to add event methods. - * - * @param {Object} [options={}] - * Options for customizing the mixin behavior. - * - * @param {String} [options.eventBusKey] - * By default, adds a `eventBusEl_` DOM element to the target object, - * which is used as an event bus. If the target object already has a - * DOM element that should be used, pass its key here. - * - * @return {Object} - * The target object. - */ -function evented(target) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - var eventBusKey = options.eventBusKey; - // Set or create the eventBusEl_. - if (eventBusKey) { - if (!target[eventBusKey].nodeName) { - throw new Error('The eventBusKey "' + eventBusKey + '" does not refer to an element.'); + PosterImage.prototype.handleClick = function handleClick(event) { + // We don't want a click to trigger playback when controls are disabled + if (!this.player_.controls()) { + return; } - target.eventBusEl_ = target[eventBusKey]; - } else { - target.eventBusEl_ = Dom.createEl('span', { className: 'vjs-event-bus' }); - } - - Obj.assign(target, EventedMixin); - - // When any evented object is disposed, it removes all its listeners. - target.on('dispose', function () { - return target.off(); - }); - - return target; -} - -exports['default'] = evented; -exports.isEvented = isEvented; - -},{"45":45,"85":85,"86":86,"88":88,"93":93}],54:[function(_dereq_,module,exports){ -'use strict'; -exports.__esModule = true; + if (this.player_.paused()) { + this.player_.play(); + } else { + this.player_.pause(); + } + }; -var _evented = _dereq_(53); + return PosterImage; +}(ClickableComponent); -var _obj = _dereq_(93); +Component.registerComponent('PosterImage', PosterImage); -var Obj = _interopRequireWildcard(_obj); +/** + * @file text-track-display.js + */ +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' +}; -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; } } +/** + * Construct an rgba color from a given hex color code. + * + * @param {number} color + * Hex number for color, like #f0e. + * + * @param {number} opacity + * Value for opacity, 0.0 - 1.0. + * + * @return {string} + * The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'. + * + * @private + */ +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 + ')'; +} /** - * Contains methods that provide statefulness to an object which is passed - * to {@link module:stateful}. + * Try to update the style of a DOM element. Some style changes will throw an error, + * particularly in IE8. Those should be noops. * - * @mixin StatefulMixin + * @param {Element} el + * The DOM element to be styled. + * + * @param {string} style + * The CSS property on the element that should be styled. + * + * @param {string} rule + * The style rule that should be applied to the property. + * + * @private */ +function tryUpdateStyle(el, style, rule) { + try { + el.style[style] = rule; + } catch (e) { + + // Satisfies linter. + return; + } +} + /** - * @file mixins/stateful.js - * @module stateful + * The component for displaying text track cues. + * + * @extends Component */ -var StatefulMixin = { - /** - * A hash containing arbitrary keys and values representing the state of - * the object. - * - * @type {Object} - */ - state: {}, +var TextTrackDisplay = function (_Component) { + inherits(TextTrackDisplay, _Component); /** - * Set the state of an object by mutating its - * {@link module:stateful~StatefulMixin.state|state} object in place. + * Creates an instance of this class. * - * @fires module:stateful~StatefulMixin#statechanged - * @param {Object|Function} stateUpdates - * A new set of properties to shallow-merge into the plugin state. - * Can be a plain object or a function returning a plain object. + * @param {Player} player + * The `Player` that this class should be attached to. * - * @returns {Object|undefined} - * An object containing changes that occurred. If no changes - * occurred, returns `undefined`. + * @param {Object} [options] + * The key/value store of player options. + * + * @param {Component~ReadyCallback} [ready] + * The function to call when `TextTrackDisplay` is ready. */ - setState: function setState(stateUpdates) { - var _this = this; - - // Support providing the `stateUpdates` state as a function. - if (typeof stateUpdates === 'function') { - stateUpdates = stateUpdates(); - } + function TextTrackDisplay(player, options, ready) { + classCallCheck(this, TextTrackDisplay); - var changes = void 0; + var _this = possibleConstructorReturn(this, _Component.call(this, player, options, ready)); - Obj.each(stateUpdates, function (value, key) { + player.on('loadstart', bind(_this, _this.toggleDisplay)); + player.on('texttrackchange', bind(_this, _this.updateDisplay)); + player.on('loadstart', bind(_this, _this.preselectTrack)); - // Record the change if the value is different from what's in the - // current state. - if (_this.state[key] !== value) { - changes = changes || {}; - changes[key] = { - from: _this.state[key], - to: value - }; + // 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(bind(_this, function () { + if (player.tech_ && player.tech_.featuresNativeTextTracks) { + this.hide(); + return; } - _this.state[key] = value; - }); + player.on('fullscreenchange', bind(this, this.updateDisplay)); - // Only trigger "statechange" if there were changes AND we have a trigger - // function. This allows us to not require that the target object be an - // evented object. - if (changes && (0, _evented.isEvented)(this)) { + var tracks = this.options_.playerOptions.tracks || []; - /** - * An event triggered on an object that is both - * {@link module:stateful|stateful} and {@link module:evented|evented} - * indicating that its state has changed. - * - * @event module:stateful~StatefulMixin#statechanged - * @type {Object} - * @property {Object} changes - * A hash containing the properties that were changed and - * the values they were changed `from` and `to`. - */ - this.trigger({ - changes: changes, - type: 'statechanged' - }); - } + for (var i = 0; i < tracks.length; i++) { + this.player_.addRemoteTextTrack(tracks[i], true); + } - return changes; + this.preselectTrack(); + })); + return _this; } -}; -/** - * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target - * object. - * - * If the target object is {@link module:evented|evented} and has a - * `handleStateChanged` method, that method will be automatically bound to the - * `statechanged` event on itself. - * - * @param {Object} target - * The object to be made stateful. - * - * @param {Object} [defaultState] - * A default set of properties to populate the newly-stateful object's - * `state` property. - * - * @returns {Object} - * Returns the `target`. - */ -function stateful(target, defaultState) { - Obj.assign(target, StatefulMixin); + /** + * Preselect a track following this precedence: + * - matches the previously selected {@link TextTrack}'s language and kind + * - matches the previously selected {@link TextTrack}'s language only + * - is the first default captions track + * - is the first default descriptions track + * + * @listens Player#loadstart + */ + + + TextTrackDisplay.prototype.preselectTrack = function preselectTrack() { + var modes = { captions: 1, subtitles: 1 }; + var trackList = this.player_.textTracks(); + var userPref = this.player_.cache_.selectedLanguage; + var firstDesc = void 0; + var firstCaptions = void 0; + var preferredTrack = void 0; + + for (var i = 0; i < trackList.length; i++) { + var track = trackList[i]; + + if (userPref && userPref.enabled && userPref.language === track.language) { + // Always choose the track that matches both language and kind + if (track.kind === userPref.kind) { + preferredTrack = track; + // or choose the first track that matches language + } else if (!preferredTrack) { + preferredTrack = track; + } - // This happens after the mixing-in because we need to replace the `state` - // added in that step. - target.state = Obj.assign({}, target.state, defaultState); + // clear everything if offTextTrackMenuItem was clicked + } else if (userPref && !userPref.enabled) { + preferredTrack = null; + firstDesc = null; + firstCaptions = null; + } else if (track['default']) { + if (track.kind === 'descriptions' && !firstDesc) { + firstDesc = track; + } else if (track.kind in modes && !firstCaptions) { + firstCaptions = track; + } + } + } - // Auto-bind the `handleStateChanged` method of the target object if it exists. - if (typeof target.handleStateChanged === 'function' && (0, _evented.isEvented)(target)) { - target.on('statechanged', target.handleStateChanged); - } + // The preferredTrack matches the user preference and takes + // precendence over all the other tracks. + // So, display the preferredTrack before the first default track + // and the subtitles/captions track before the descriptions track + if (preferredTrack) { + preferredTrack.mode = 'showing'; + } else if (firstCaptions) { + firstCaptions.mode = 'showing'; + } else if (firstDesc) { + firstDesc.mode = 'showing'; + } + }; - return target; -} + /** + * Turn display of {@link TextTrack}'s from the current state into the other state. + * There are only two states: + * - 'shown' + * - 'hidden' + * + * @listens Player#loadstart + */ -exports['default'] = stateful; -},{"53":53,"93":93}],55:[function(_dereq_,module,exports){ -'use strict'; + TextTrackDisplay.prototype.toggleDisplay = function toggleDisplay() { + if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) { + this.hide(); + } else { + this.show(); + } + }; -exports.__esModule = true; + /** + * Create the {@link Component}'s DOM element. + * + * @return {Element} + * The element that was created. + */ -var _dom = _dereq_(85); -var Dom = _interopRequireWildcard(_dom); + TextTrackDisplay.prototype.createEl = function createEl() { + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-text-track-display' + }, { + 'aria-live': 'off', + 'aria-atomic': 'true' + }); + }; -var _fn = _dereq_(88); + /** + * Clear all displayed {@link TextTrack}s. + */ -var Fn = _interopRequireWildcard(_fn); -var _component = _dereq_(5); + TextTrackDisplay.prototype.clearDisplay = function clearDisplay() { + if (typeof window_1.WebVTT === 'function') { + window_1.WebVTT.processCues(window_1, [], this.el_); + } + }; -var _component2 = _interopRequireDefault(_component); + /** + * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or + * a {@link Player#fullscreenchange} is fired. + * + * @listens Player#texttrackchange + * @listens Player#fullscreenchange + */ -var _window = _dereq_(100); -var _window2 = _interopRequireDefault(_window); + TextTrackDisplay.prototype.updateDisplay = function updateDisplay() { + var tracks = this.player_.textTracks(); -var _document = _dereq_(99); + this.clearDisplay(); -var _document2 = _interopRequireDefault(_document); + // 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' -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + var descriptionsTrack = null; + var captionsSubtitlesTrack = null; + var i = tracks.length; -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; } } + while (i--) { + var track = tracks[i]; -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + if (track.mode === 'showing') { + if (track.kind === 'descriptions') { + descriptionsTrack = track; + } else { + captionsSubtitlesTrack = track; + } + } + } -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + if (captionsSubtitlesTrack) { + if (this.getAttribute('aria-live') !== 'off') { + this.setAttribute('aria-live', 'off'); + } + this.updateForTrack(captionsSubtitlesTrack); + } else if (descriptionsTrack) { + if (this.getAttribute('aria-live') !== 'assertive') { + this.setAttribute('aria-live', 'assertive'); + } + this.updateForTrack(descriptionsTrack); + } + }; -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; } /** - * @file modal-dialog.js - */ + /** + * Add an {@link Texttrack} to to the {@link Tech}s {@link TextTrackList}. + * + * @param {TextTrack} track + * Text track object to be added to the list. + */ -var MODAL_CLASS_NAME = 'vjs-modal-dialog'; -var ESC = 27; + TextTrackDisplay.prototype.updateForTrack = function updateForTrack(track) { + if (typeof window_1.WebVTT !== 'function' || !track.activeCues) { + return; + } + + var cues = []; + + for (var _i = 0; _i < track.activeCues.length; _i++) { + cues.push(track.activeCues[_i]); + } + + window_1.WebVTT.processCues(window_1, cues, this.el_); + + if (!this.player_.textTrackSettings) { + return; + } + + var overrides = this.player_.textTrackSettings.getValues(); + + 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 = window_1.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; +}(Component); + +Component.registerComponent('TextTrackDisplay', TextTrackDisplay); /** - * 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. + * @file loading-spinner.js + */ +/** + * A loading spinner for use during waiting/loading events. * * @extends Component */ -var ModalDialog = function (_Component) { - _inherits(ModalDialog, _Component); +var LoadingSpinner = function (_Component) { + inherits(LoadingSpinner, _Component); + + function LoadingSpinner() { + classCallCheck(this, LoadingSpinner); + return possibleConstructorReturn(this, _Component.apply(this, arguments)); + } /** - * Create an instance of this class. - * - * @param {Player} player - * The `Player` that this class should be attached to. - * - * @param {Object} [options] - * The key/value store of player 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. + * Create the `LoadingSpinner`s DOM element. * - * @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. + * @return {Element} + * The dom element that gets created. */ - function ModalDialog(player, options) { - _classCallCheck(this, ModalDialog); + LoadingSpinner.prototype.createEl = function createEl$$1() { + var isAudio = this.player_.isAudio(); + var playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player'); + var controlText = createEl('span', { + className: 'vjs-control-text', + innerHTML: this.localize('{1} is loading.', [playerType]) + }); - var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); + var el = _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-loading-spinner', + dir: 'ltr' + }); - _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false; + el.appendChild(controlText); - _this.closeable(!_this.options_.uncloseable); - _this.content(_this.options_.content); + return el; + }; - // 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' - }); + return LoadingSpinner; +}(Component); - _this.descEl_ = Dom.createEl('p', { - className: MODAL_CLASS_NAME + '-description vjs-control-text', - id: _this.el().getAttribute('aria-describedby') - }); +Component.registerComponent('LoadingSpinner', LoadingSpinner); - Dom.textContent(_this.descEl_, _this.description()); - _this.el_.appendChild(_this.descEl_); - _this.el_.appendChild(_this.contentEl_); - return _this; +/** + * @file button.js + */ +/** + * Base class for all buttons. + * + * @extends ClickableComponent + */ + +var Button = function (_ClickableComponent) { + inherits(Button, _ClickableComponent); + + function Button() { + classCallCheck(this, Button); + return possibleConstructorReturn(this, _ClickableComponent.apply(this, arguments)); } /** - * Create the `ModalDialog`'s DOM element + * Create the `Button`s DOM element. + * + * @param {string} [tag="button"] + * The element's node type. This argument is IGNORED: no matter what + * is passed, it will always create a `button` element. + * + * @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} - * The DOM element that gets created. + * The element that gets created. */ + Button.prototype.createEl = function createEl(tag) { + var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + tag = 'button'; - 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' - }); - }; + props = assign({ + innerHTML: '', + className: this.buildCSSClass() + }, props); - /** - * Builds the default DOM `className`. - * - * @return {string} - * The DOM `className` for this object. - */ + // Add attributes for button element + attributes = assign({ + // Necessary since the default button type is "submit" + 'type': 'button', - ModalDialog.prototype.buildCSSClass = function buildCSSClass() { - return MODAL_CLASS_NAME + ' vjs-hidden ' + _Component.prototype.buildCSSClass.call(this); + // let the screen reader user know that the text of the button may change + 'aria-live': 'polite' + }, attributes); + + var el = Component.prototype.createEl.call(this, tag, props, attributes); + + this.createControlTextEl(el); + + return el; }; /** - * Handles `keydown` events on the document, looking for ESC, which closes - * the modal. + * Add a child `Component` inside of this `Button`. * - * @param {EventTarget~Event} e - * The keypress that triggered this event. + * @param {string|Component} child + * The name or instance of a child to add. * - * @listens keydown + * @param {Object} [options={}] + * The key/value store of options that will get passed to children of + * the child. + * + * @return {Component} + * The `Component` that gets added as a child. When using a string the + * `Component` will get created by this process. + * + * @deprecated since version 5 */ - ModalDialog.prototype.handleKeyPress = function handleKeyPress(e) { - if (e.which === ESC && this.closeable()) { - this.close(); - } + Button.prototype.addChild = function addChild(child) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + var className = this.constructor.name; + + log$1.warn('Adding an actionable (user controllable) child to a Button (' + className + ') is not supported; use a ClickableComponent instead.'); + + // Avoid the error message generated by ClickableComponent's addChild method + return Component.prototype.addChild.call(this, child, options); }; /** - * Returns the label string for this modal. Primarily used for accessibility. - * - * @return {string} - * the localized or raw label of this modal. + * Enable the `Button` element so that it can be activated or clicked. Use this with + * {@link Button#disable}. */ - ModalDialog.prototype.label = function label() { - return this.localize(this.options_.label || 'Modal Window'); + Button.prototype.enable = function enable() { + _ClickableComponent.prototype.enable.call(this); + this.el_.removeAttribute('disabled'); }; /** - * Returns the description string for this modal. Primarily used for - * accessibility. - * - * @return {string} - * The localized or raw description of this modal. + * Disable the `Button` element so that it cannot be activated or clicked. Use this with + * {@link Button#enable}. */ - 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; + Button.prototype.disable = function disable() { + _ClickableComponent.prototype.disable.call(this); + this.el_.setAttribute('disabled', 'disabled'); }; /** - * Opens the modal. + * This gets called when a `Button` has focus and `keydown` is triggered via a key + * press. * - * @fires ModalDialog#beforemodalopen - * @fires ModalDialog#modalopen + * @param {EventTarget~Event} event + * The event that caused this function to get called. + * + * @listens keydown */ - ModalDialog.prototype.open = function open() { - if (!this.opened_) { - var player = this.player(); + Button.prototype.handleKeyPress = function handleKeyPress(event) { - /** - * Fired just before a `ModalDialog` is opened. - * - * @event ModalDialog#beforemodalopen - * @type {EventTarget~Event} - */ - this.trigger('beforemodalopen'); - this.opened_ = true; + // Ignore Space (32) or Enter (13) key operation, which is handled by the browser for a button. + if (event.which === 32 || event.which === 13) { + return; + } - // Fill content if the modal has never opened before and - // never been filled. - if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) { - this.fill(); - } + // Pass keypress handling up for unsupported keys + _ClickableComponent.prototype.handleKeyPress.call(this, event); + }; - // If the player was playing, pause it and take note of its previously - // playing state. - this.wasPlaying_ = !player.paused(); + return Button; +}(ClickableComponent); - if (this.options_.pauseOnOpen && this.wasPlaying_) { - player.pause(); - } +Component.registerComponent('Button', Button); - if (this.closeable()) { - this.on(this.el_.ownerDocument, 'keydown', Fn.bind(this, this.handleKeyPress)); - } +/** + * @file big-play-button.js + */ +/** + * The initial play button that shows before the video has played. The hiding of the + * `BigPlayButton` get done via CSS and `Player` states. + * + * @extends Button + */ - player.controls(false); - this.show(); - this.conditionalFocus_(); - this.el().setAttribute('aria-hidden', 'false'); +var BigPlayButton = function (_Button) { + inherits(BigPlayButton, _Button); - /** - * Fired just after a `ModalDialog` is opened. - * - * @event ModalDialog#modalopen - * @type {EventTarget~Event} - */ - this.trigger('modalopen'); - this.hasBeenOpened_ = true; - } - }; + function BigPlayButton(player, options) { + classCallCheck(this, BigPlayButton); + + var _this = possibleConstructorReturn(this, _Button.call(this, player, options)); + + _this.mouseused_ = false; + + _this.on('mousedown', _this.handleMouseDown); + return _this; + } /** - * If the `ModalDialog` is currently open or closed. - * - * @param {boolean} [value] - * If given, it will open (`true`) or close (`false`) the modal. + * Builds the default DOM `className`. * - * @return {boolean} - * the current open state of the modaldialog + * @return {string} + * The DOM `className` for this object. Always returns 'vjs-big-play-button'. */ - ModalDialog.prototype.opened = function opened(value) { - if (typeof value === 'boolean') { - this[value ? 'open' : 'close'](); - } - return this.opened_; + BigPlayButton.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-big-play-button'; }; /** - * Closes the modal, does nothing if the `ModalDialog` is - * not open. + * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent} + * for more detailed information on what a click can be. * - * @fires ModalDialog#beforemodalclose - * @fires ModalDialog#modalclose + * @param {EventTarget~Event} event + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click */ - ModalDialog.prototype.close = function close() { - if (!this.opened_) { + BigPlayButton.prototype.handleClick = function handleClick(event) { + var playPromise = this.player_.play(); + + // exit early if clicked via the mouse + if (this.mouseused_ && event.clientX && event.clientY) { return; } - var player = this.player(); - /** - * Fired just before a `ModalDialog` is closed. - * - * @event ModalDialog#beforemodalclose - * @type {EventTarget~Event} - */ - this.trigger('beforemodalclose'); - this.opened_ = false; + var cb = this.player_.getChild('controlBar'); + var playToggle = cb && cb.getChild('playToggle'); - if (this.wasPlaying_ && this.options_.pauseOnOpen) { - player.play(); + if (!playToggle) { + this.player_.focus(); + return; } - if (this.closeable()) { - this.off(this.el_.ownerDocument, 'keydown', Fn.bind(this, this.handleKeyPress)); + var playFocus = function playFocus() { + return playToggle.focus(); + }; + + if (isPromise(playPromise)) { + playPromise.then(playFocus, function () {}); + } else { + this.setTimeout(playFocus, 1); } + }; - player.controls(true); - this.hide(); - this.el().setAttribute('aria-hidden', 'true'); + BigPlayButton.prototype.handleKeyPress = function handleKeyPress(event) { + this.mouseused_ = false; - /** - * Fired just after a `ModalDialog` is closed. - * - * @event ModalDialog#modalclose - * @type {EventTarget~Event} - */ - this.trigger('modalclose'); - this.conditionalBlur_(); + _Button.prototype.handleKeyPress.call(this, event); + }; - if (this.options_.temporary) { - this.dispose(); - } + BigPlayButton.prototype.handleMouseDown = function handleMouseDown(event) { + this.mouseused_ = true; }; - /** - * Check to see if the `ModalDialog` is closeable via the UI. - * - * @param {boolean} [value] - * If given as a boolean, it will set the `closeable` option. - * - * @return {boolean} - * Returns the final value of the closable option. - */ + return BigPlayButton; +}(Button); +/** + * The text that should display over the `BigPlayButton`s controls. Added to for localization. + * + * @type {string} + * @private + */ - 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) { +BigPlayButton.prototype.controlText_ = 'Play Video'; - // The close button should be a child of the modal - not its - // content element, so temporarily change the content element. - var temp = this.contentEl_; +Component.registerComponent('BigPlayButton', BigPlayButton); - this.contentEl_ = this.el_; - close = this.addChild('closeButton', { controlText: 'Close Modal Dialog' }); - this.contentEl_ = temp; - this.on(close, 'close', this.close); - } +/** + * @file close-button.js + */ +/** + * The `CloseButton` is a `{@link Button}` that fires a `close` event when + * it gets clicked. + * + * @extends Button + */ - // 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_; - }; +var CloseButton = function (_Button) { + inherits(CloseButton, _Button); /** - * Fill the modal's content element with the modal's "content" option. - * The content element will be emptied before this change takes place. + * Creates an instance of the this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. */ + function CloseButton(player, options) { + classCallCheck(this, CloseButton); + var _this = possibleConstructorReturn(this, _Button.call(this, player, options)); - ModalDialog.prototype.fill = function fill() { - this.fillWith(this.content()); - }; + _this.controlText(options && options.controlText || _this.localize('Close')); + return _this; + } /** - * Fill the modal's content element with arbitrary content. - * The content element will be emptied before this change takes place. - * - * @fires ModalDialog#beforemodalfill - * @fires ModalDialog#modalfill + * Builds the default DOM `className`. * - * @param {Mixed} [content] - * The same rules apply to this as apply to the `content` option. + * @return {string} + * The DOM `className` for this object. */ - ModalDialog.prototype.fillWith = function fillWith(content) { - var contentEl = this.contentEl(); - var parentEl = contentEl.parentNode; - var nextSiblingEl = contentEl.nextSibling; - - /** - * Fired just before a `ModalDialog` is filled with content. - * - * @event ModalDialog#beforemodalfill - * @type {EventTarget~Event} - */ - 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); - /** - * Fired just after a `ModalDialog` is filled with content. - * - * @event ModalDialog#modalfill - * @type {EventTarget~Event} - */ - this.trigger('modalfill'); - - // Re-inject the re-filled content element. - if (nextSiblingEl) { - parentEl.insertBefore(contentEl, nextSiblingEl); - } else { - parentEl.appendChild(contentEl); - } - - // make sure that the close button is last in the dialog DOM - var closeButton = this.getChild('closeButton'); - - if (closeButton) { - parentEl.appendChild(closeButton.el_); - } + CloseButton.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-close-button ' + _Button.prototype.buildCSSClass.call(this); }; /** - * Empties the content element. This happens anytime the modal is filled. + * This gets called when a `CloseButton` gets clicked. See + * {@link ClickableComponent#handleClick} for more information on when this will be + * triggered * - * @fires ModalDialog#beforemodalempty - * @fires ModalDialog#modalempty + * @param {EventTarget~Event} event + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click + * @fires CloseButton#close */ - ModalDialog.prototype.empty = function empty() { - /** - * Fired just before a `ModalDialog` is emptied. - * - * @event ModalDialog#beforemodalempty - * @type {EventTarget~Event} - */ - this.trigger('beforemodalempty'); - Dom.emptyEl(this.contentEl()); + CloseButton.prototype.handleClick = function handleClick(event) { /** - * Fired just after a `ModalDialog` is emptied. + * Triggered when the a `CloseButton` is clicked. * - * @event ModalDialog#modalempty + * @event CloseButton#close * @type {EventTarget~Event} + * + * @property {boolean} [bubbles=false] + * set to false so that the close event does not + * bubble up to parents if there is no listener */ - this.trigger('modalempty'); + this.trigger({ type: 'close', bubbles: false }); }; + return CloseButton; +}(Button); + +Component.registerComponent('CloseButton', CloseButton); + +/** + * @file play-toggle.js + */ +/** + * Button to toggle between play and pause. + * + * @extends Button + */ + +var PlayToggle = function (_Button) { + inherits(PlayToggle, _Button); + /** - * 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. + * Creates an instance of this class. * - * @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`. + * @param {Player} player + * The `Player` that this class should be attached to. * - * @return {Mixed} - * The current content of the modal dialog + * @param {Object} [options] + * The key/value store of player options. */ + function PlayToggle(player, options) { + classCallCheck(this, PlayToggle); + var _this = possibleConstructorReturn(this, _Button.call(this, player, options)); - ModalDialog.prototype.content = function content(value) { - if (typeof value !== 'undefined') { - this.content_ = value; - } - return this.content_; - }; + _this.on(player, 'play', _this.handlePlay); + _this.on(player, 'pause', _this.handlePause); + _this.on(player, 'ended', _this.handleEnded); + return _this; + } /** - * conditionally focus the modal dialog if focus was previously on the player. + * Builds the default DOM `className`. * - * @private + * @return {string} + * The DOM `className` for this object. */ - ModalDialog.prototype.conditionalFocus_ = function conditionalFocus_() { - var activeEl = _document2['default'].activeElement; - var playerEl = this.player_.el_; - - this.previouslyActiveEl_ = null; + PlayToggle.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-play-control ' + _Button.prototype.buildCSSClass.call(this); + }; - if (playerEl.contains(activeEl) || playerEl === activeEl) { - this.previouslyActiveEl_ = activeEl; + /** + * This gets called when an `PlayToggle` is "clicked". See + * {@link ClickableComponent} for more detailed information on what a click can be. + * + * @param {EventTarget~Event} [event] + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click + */ - this.focus(); - this.on(_document2['default'], 'keydown', this.handleKeyDown); + PlayToggle.prototype.handleClick = function handleClick(event) { + if (this.player_.paused()) { + this.player_.play(); + } else { + this.player_.pause(); } }; /** - * conditionally blur the element and refocus the last focused element + * This gets called once after the video has ended and the user seeks so that + * we can change the replay button back to a play button. * - * @private + * @param {EventTarget~Event} [event] + * The event that caused this function to run. + * + * @listens Player#seeked */ - ModalDialog.prototype.conditionalBlur_ = function conditionalBlur_() { - if (this.previouslyActiveEl_) { - this.previouslyActiveEl_.focus(); - this.previouslyActiveEl_ = null; - } + PlayToggle.prototype.handleSeeked = function handleSeeked(event) { + this.removeClass('vjs-ended'); - this.off(_document2['default'], 'keydown', this.handleKeyDown); + if (this.player_.paused()) { + this.handlePause(event); + } else { + this.handlePlay(event); + } }; /** - * Keydown handler. Attached when modal is focused. + * Add the vjs-playing class to the element so it can change appearance. * - * @listens keydown + * @param {EventTarget~Event} [event] + * The event that caused this function to run. + * + * @listens Player#play */ - ModalDialog.prototype.handleKeyDown = function handleKeyDown(event) { - // exit early if it isn't a tab key - if (event.which !== 9) { - return; - } - - var focusableEls = this.focusableEls_(); - var activeEl = this.el_.querySelector(':focus'); - var focusIndex = void 0; + PlayToggle.prototype.handlePlay = function handlePlay(event) { + this.removeClass('vjs-ended'); + this.removeClass('vjs-paused'); + this.addClass('vjs-playing'); + // change the button text to "Pause" + this.controlText('Pause'); + }; - for (var i = 0; i < focusableEls.length; i++) { - if (activeEl === focusableEls[i]) { - focusIndex = i; - break; - } - } + /** + * Add the vjs-paused class to the element so it can change appearance. + * + * @param {EventTarget~Event} [event] + * The event that caused this function to run. + * + * @listens Player#pause + */ - if (_document2['default'].activeElement === this.el_) { - focusIndex = 0; - } - if (event.shiftKey && focusIndex === 0) { - focusableEls[focusableEls.length - 1].focus(); - event.preventDefault(); - } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) { - focusableEls[0].focus(); - event.preventDefault(); - } + PlayToggle.prototype.handlePause = function handlePause(event) { + this.removeClass('vjs-playing'); + this.addClass('vjs-paused'); + // change the button text to "Play" + this.controlText('Play'); }; /** - * get all focusable elements + * Add the vjs-ended class to the element so it can change appearance * - * @private + * @param {EventTarget~Event} [event] + * The event that caused this function to run. + * + * @listens Player#ended */ - ModalDialog.prototype.focusableEls_ = function focusableEls_() { - var allChildren = this.el_.querySelectorAll('*'); + PlayToggle.prototype.handleEnded = function handleEnded(event) { + this.removeClass('vjs-playing'); + this.addClass('vjs-ended'); + // change the button text to "Replay" + this.controlText('Replay'); - return Array.prototype.filter.call(allChildren, function (child) { - return (child instanceof _window2['default'].HTMLAnchorElement || child instanceof _window2['default'].HTMLAreaElement) && child.hasAttribute('href') || (child instanceof _window2['default'].HTMLInputElement || child instanceof _window2['default'].HTMLSelectElement || child instanceof _window2['default'].HTMLTextAreaElement || child instanceof _window2['default'].HTMLButtonElement) && !child.hasAttribute('disabled') || child instanceof _window2['default'].HTMLIFrameElement || child instanceof _window2['default'].HTMLObjectElement || child instanceof _window2['default'].HTMLEmbedElement || child.hasAttribute('tabindex') && child.getAttribute('tabindex') !== -1 || child.hasAttribute('contenteditable'); - }); + // on the next seek remove the replay button + this.one(this.player_, 'seeked', this.handleSeeked); }; - return ModalDialog; -}(_component2['default']); + return PlayToggle; +}(Button); /** - * Default options for `ModalDialog` default options. + * The text that should display over the `PlayToggle`s controls. Added for localization. * - * @type {Object} + * @type {string} * @private */ -ModalDialog.prototype.options_ = { - pauseOnOpen: true, - temporary: true -}; - -_component2['default'].registerComponent('ModalDialog', ModalDialog); -exports['default'] = ModalDialog; - -},{"100":100,"5":5,"85":85,"88":88,"99":99}],56:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _templateObject = _taggedTemplateLiteralLoose(['\n Using the tech directly can be dangerous. I hope you know what you\'re doing.\n See https://github.com/videojs/video.js/issues/2617 for more info.\n '], ['\n Using the tech directly can be dangerous. I hope you know what you\'re doing.\n See https://github.com/videojs/video.js/issues/2617 for more info.\n ']); - -var _component = _dereq_(5); +PlayToggle.prototype.controlText_ = 'Play'; -var _component2 = _interopRequireDefault(_component); +Component.registerComponent('PlayToggle', PlayToggle); -var _document = _dereq_(99); +/** + * @file format-time.js + * @module Format-time + */ -var _document2 = _interopRequireDefault(_document); +/** + * 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 + */ +function formatTime(seconds) { + var guide = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : seconds; -var _window = _dereq_(100); + 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; +} + +/** + * @file time-display.js + */ +/** + * Displays the time left in the video + * + * @extends Component + */ + +var TimeDisplay = function (_Component) { + inherits(TimeDisplay, _Component); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function TimeDisplay(player, options) { + classCallCheck(this, TimeDisplay); -var _window2 = _interopRequireDefault(_window); + var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); -var _tsml = _dereq_(103); + _this.throttledUpdateContent = throttle(bind(_this, _this.updateContent), 25); + _this.on(player, 'timeupdate', _this.throttledUpdateContent); + return _this; + } -var _tsml2 = _interopRequireDefault(_tsml); + /** + * Create the `Component`'s DOM element + * + * @return {Element} + * The element that was created. + */ -var _evented = _dereq_(53); -var _evented2 = _interopRequireDefault(_evented); + TimeDisplay.prototype.createEl = function createEl$$1(plainName) { + var className = this.buildCSSClass(); + var el = _Component.prototype.createEl.call(this, 'div', { + className: className + ' vjs-time-control vjs-control' + }); -var _events = _dereq_(86); + this.contentEl_ = createEl('div', { + className: className + '-display' + }, { + // tell screen readers not to automatically read the time as it changes + 'aria-live': 'off' + }, createEl('span', { + className: 'vjs-control-text', + textContent: this.localize(this.controlText_) + })); -var Events = _interopRequireWildcard(_events); + this.updateTextNode_(); + el.appendChild(this.contentEl_); + return el; + }; -var _dom = _dereq_(85); + TimeDisplay.prototype.dispose = function dispose() { + this.contentEl_ = null; + this.textNode_ = null; -var Dom = _interopRequireWildcard(_dom); + _Component.prototype.dispose.call(this); + }; -var _fn = _dereq_(88); + /** + * Updates the "remaining time" text node with new content using the + * contents of the `formattedTime_` property. + * + * @private + */ -var Fn = _interopRequireWildcard(_fn); -var _guid = _dereq_(90); + TimeDisplay.prototype.updateTextNode_ = function updateTextNode_() { + if (!this.contentEl_) { + return; + } -var Guid = _interopRequireWildcard(_guid); + while (this.contentEl_.firstChild) { + this.contentEl_.removeChild(this.contentEl_.firstChild); + } -var _browser = _dereq_(81); + this.textNode_ = document_1.createTextNode(this.formattedTime_ || '0:00'); + this.contentEl_.appendChild(this.textNode_); + }; -var browser = _interopRequireWildcard(_browser); + /** + * Generates a formatted time for this component to use in display. + * + * @param {number} time + * A numeric time, in seconds. + * + * @return {string} + * A formatted time + * + * @private + */ -var _log = _dereq_(91); -var _log2 = _interopRequireDefault(_log); + TimeDisplay.prototype.formatTime_ = function formatTime_(time) { + return formatTime(time); + }; -var _toTitleCase = _dereq_(96); + /** + * Updates the time display text node if it has what was passed in changed + * the formatted time. + * + * @param {number} time + * The time to update to + * + * @private + */ -var _toTitleCase2 = _interopRequireDefault(_toTitleCase); -var _timeRanges = _dereq_(95); + TimeDisplay.prototype.updateFormattedTime_ = function updateFormattedTime_(time) { + var formattedTime = this.formatTime_(time); -var _buffer = _dereq_(82); + if (formattedTime === this.formattedTime_) { + return; + } -var _stylesheet = _dereq_(94); + this.formattedTime_ = formattedTime; + this.requestAnimationFrame(this.updateTextNode_); + }; -var stylesheet = _interopRequireWildcard(_stylesheet); + /** + * To be filled out in the child class, should update the displayed time + * in accordance with the fact that the current time has changed. + * + * @param {EventTarget~Event} [event] + * The `timeupdate` event that caused this to run. + * + * @listens Player#timeupdate + */ -var _fullscreenApi = _dereq_(47); -var _fullscreenApi2 = _interopRequireDefault(_fullscreenApi); + TimeDisplay.prototype.updateContent = function updateContent(event) {}; -var _mediaError = _dereq_(49); + return TimeDisplay; +}(Component); -var _mediaError2 = _interopRequireDefault(_mediaError); +/** + * The text that should display over the `TimeDisplay`s controls. Added to for localization. + * + * @type {string} + * @private + */ -var _tuple = _dereq_(102); -var _tuple2 = _interopRequireDefault(_tuple); +TimeDisplay.prototype.controlText_ = 'Time'; -var _obj = _dereq_(93); +Component.registerComponent('TimeDisplay', TimeDisplay); -var _mergeOptions = _dereq_(92); +/** + * @file current-time-display.js + */ +/** + * Displays the current time + * + * @extends Component + */ -var _mergeOptions2 = _interopRequireDefault(_mergeOptions); +var CurrentTimeDisplay = function (_TimeDisplay) { + inherits(CurrentTimeDisplay, _TimeDisplay); -var _textTrackListConverter = _dereq_(71); + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function CurrentTimeDisplay(player, options) { + classCallCheck(this, CurrentTimeDisplay); -var _textTrackListConverter2 = _interopRequireDefault(_textTrackListConverter); + var _this = possibleConstructorReturn(this, _TimeDisplay.call(this, player, options)); -var _modalDialog = _dereq_(55); + _this.on(player, 'ended', _this.handleEnded); + return _this; + } -var _modalDialog2 = _interopRequireDefault(_modalDialog); + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ -var _tech = _dereq_(64); -var _tech2 = _interopRequireDefault(_tech); + CurrentTimeDisplay.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-current-time'; + }; -var _middleware = _dereq_(63); + /** + * Update current time display + * + * @param {EventTarget~Event} [event] + * The `timeupdate` event that caused this function to run. + * + * @listens Player#timeupdate + */ -var middleware = _interopRequireWildcard(_middleware); -var _trackTypes = _dereq_(77); + CurrentTimeDisplay.prototype.updateContent = function updateContent(event) { + // Allows for smooth scrubbing, when player can't keep up. + var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); -var _filterSource = _dereq_(87); + this.updateFormattedTime_(time); + }; -var _filterSource2 = _interopRequireDefault(_filterSource); + /** + * When the player fires ended there should be no time left. Sadly + * this is not always the case, lets make it seem like that is the case + * for users. + * + * @param {EventTarget~Event} [event] + * The `ended` event that caused this to run. + * + * @listens Player#ended + */ -_dereq_(62); -_dereq_(58); + CurrentTimeDisplay.prototype.handleEnded = function handleEnded(event) { + if (!this.player_.duration()) { + return; + } + this.updateFormattedTime_(this.player_.duration()); + }; -_dereq_(70); + return CurrentTimeDisplay; +}(TimeDisplay); -_dereq_(48); +/** + * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization. + * + * @type {string} + * @private + */ -_dereq_(1); -_dereq_(4); +CurrentTimeDisplay.prototype.controlText_ = 'Current Time'; -_dereq_(8); +Component.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay); -_dereq_(44); +/** + * @file duration-display.js + */ +/** + * Displays the duration + * + * @extends Component + */ -_dereq_(73); +var DurationDisplay = function (_TimeDisplay) { + inherits(DurationDisplay, _TimeDisplay); -_dereq_(61); + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function DurationDisplay(player, options) { + classCallCheck(this, DurationDisplay); -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; } } + // we do not want to/need to throttle duration changes, + // as they should always display the changed duration as + // it has changed + var _this = possibleConstructorReturn(this, _TimeDisplay.call(this, player, options)); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + _this.on(player, 'durationchange', _this.updateContent); -function _taggedTemplateLiteralLoose(strings, raw) { strings.raw = raw; return strings; } + // Also listen for timeupdate (in the parent) and loadedmetadata because removing those + // listeners could have broken dependent applications/libraries. These + // can likely be removed for 7.0. + _this.on(player, 'loadedmetadata', _this.throttledUpdateContent); + return _this; + } -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } -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; } /** - * @file player.js - */ -// Subclasses Component + DurationDisplay.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-duration'; + }; + /** + * Update duration time display. + * + * @param {EventTarget~Event} [event] + * The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused + * this function to be called. + * + * @listens Player#durationchange + * @listens Player#timeupdate + * @listens Player#loadedmetadata + */ -// The following imports are used only to ensure that the corresponding modules -// are always included in the video.js package. Importing the modules will -// execute them and they will register themselves with video.js. + DurationDisplay.prototype.updateContent = function updateContent(event) { + var duration = this.player_.duration(); -// Import Html5 tech, at least for disposing the original video tag. + if (duration && this.duration_ !== duration) { + this.duration_ = duration; + this.updateFormattedTime_(duration); + } + }; + return DurationDisplay; +}(TimeDisplay); -// The following tech events are simply re-triggered -// on the player when they happen -var TECH_EVENTS_RETRIGGER = [ /** - * Fired while the user agent is downloading media data. - * - * @event Player#progress - * @type {EventTarget~Event} - */ -/** - * Retrigger the `progress` event that was triggered by the {@link Tech}. + * The text that should display over the `DurationDisplay`s controls. Added to for localization. * + * @type {string} * @private - * @method Player#handleTechProgress_ - * @fires Player#progress - * @listens Tech#progress */ -'progress', -/** - * Fires when the loading of an audio/video is aborted. - * - * @event Player#abort - * @type {EventTarget~Event} - */ -/** - * Retrigger the `abort` event that was triggered by the {@link Tech}. - * - * @private - * @method Player#handleTechAbort_ - * @fires Player#abort - * @listens Tech#abort - */ -'abort', + +DurationDisplay.prototype.controlText_ = 'Duration Time'; + +Component.registerComponent('DurationDisplay', DurationDisplay); /** - * Fires when the browser is intentionally not getting media data. - * - * @event Player#suspend - * @type {EventTarget~Event} + * @file time-divider.js */ /** - * Retrigger the `suspend` event that was triggered by the {@link Tech}. + * The separator between the current time and duration. + * Can be hidden if it's not needed in the design. * - * @private - * @method Player#handleTechSuspend_ - * @fires Player#suspend - * @listens Tech#suspend + * @extends Component */ -'suspend', -/** - * Fires when the current playlist is empty. - * - * @event Player#emptied - * @type {EventTarget~Event} - */ -/** - * Retrigger the `emptied` event that was triggered by the {@link Tech}. - * - * @private - * @method Player#handleTechEmptied_ - * @fires Player#emptied - * @listens Tech#emptied - */ -'emptied', -/** - * Fires when the browser is trying to get media data, but data is not available. - * - * @event Player#stalled - * @type {EventTarget~Event} - */ -/** - * Retrigger the `stalled` event that was triggered by the {@link Tech}. - * - * @private - * @method Player#handleTechStalled_ - * @fires Player#stalled - * @listens Tech#stalled - */ -'stalled', - -/** - * Fires when the browser has loaded meta data for the audio/video. - * - * @event Player#loadedmetadata - * @type {EventTarget~Event} - */ -/** - * Retrigger the `stalled` event that was triggered by the {@link Tech}. - * - * @private - * @method Player#handleTechLoadedmetadata_ - * @fires Player#loadedmetadata - * @listens Tech#loadedmetadata - */ -'loadedmetadata', - -/** - * Fires when the browser has loaded the current frame of the audio/video. - * - * @event player#loadeddata - * @type {event} - */ -/** - * Retrigger the `loadeddata` event that was triggered by the {@link Tech}. - * - * @private - * @method Player#handleTechLoaddeddata_ - * @fires Player#loadeddata - * @listens Tech#loadeddata - */ -'loadeddata', +var TimeDivider = function (_Component) { + inherits(TimeDivider, _Component); -/** - * Fires when the current playback position has changed. - * - * @event player#timeupdate - * @type {event} - */ -/** - * Retrigger the `timeupdate` event that was triggered by the {@link Tech}. - * - * @private - * @method Player#handleTechTimeUpdate_ - * @fires Player#timeupdate - * @listens Tech#timeupdate - */ -'timeupdate', + function TimeDivider() { + classCallCheck(this, TimeDivider); + return possibleConstructorReturn(this, _Component.apply(this, arguments)); + } -/** - * Fires when the playing speed of the audio/video is changed - * - * @event player#ratechange - * @type {event} - */ -/** - * Retrigger the `ratechange` event that was triggered by the {@link Tech}. - * - * @private - * @method Player#handleTechRatechange_ - * @fires Player#ratechange - * @listens Tech#ratechange - */ -'ratechange', + /** + * Create the component's DOM element + * + * @return {Element} + * The element that was created. + */ + TimeDivider.prototype.createEl = function createEl() { + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-time-control vjs-time-divider', + innerHTML: '
    /
    ' + }); + }; -/** - * Fires when the video's intrinsic dimensions change - * - * @event Player#resize - * @type {event} - */ -/** - * Retrigger the `resize` event that was triggered by the {@link Tech}. - * - * @private - * @method Player#handleTechResize_ - * @fires Player#resize - * @listens Tech#resize - */ -'resize', + return TimeDivider; +}(Component); -/** - * Fires when the volume has been changed - * - * @event player#volumechange - * @type {event} - */ -/** - * Retrigger the `volumechange` event that was triggered by the {@link Tech}. - * - * @private - * @method Player#handleTechVolumechange_ - * @fires Player#volumechange - * @listens Tech#volumechange - */ -'volumechange', +Component.registerComponent('TimeDivider', TimeDivider); /** - * Fires when the text track has been changed - * - * @event player#texttrackchange - * @type {event} - */ -/** - * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}. - * - * @private - * @method Player#handleTechTexttrackchange_ - * @fires Player#texttrackchange - * @listens Tech#texttrackchange + * @file remaining-time-display.js */ -'texttrackchange']; - /** - * An instance of the `Player` class is created when any of the Video.js setup methods - * are used to initialize a video. - * - * After an instance has been created it can be accessed globally in two ways: - * 1. By calling `videojs('example_video_1');` - * 2. By using it directly via `videojs.players.example_video_1;` + * Displays the time left in the video * * @extends Component */ -var Player = function (_Component) { - _inherits(Player, _Component); +var RemainingTimeDisplay = function (_TimeDisplay) { + inherits(RemainingTimeDisplay, _TimeDisplay); /** - * Create an instance of this class. + * Creates an instance of this class. * - * @param {Element} tag - * The original video DOM element used for configuring options. + * @param {Player} player + * The `Player` that this class should be attached to. * * @param {Object} [options] - * Object of option names and values. + * The key/value store of player options. + */ + function RemainingTimeDisplay(player, options) { + classCallCheck(this, RemainingTimeDisplay); + + var _this = possibleConstructorReturn(this, _TimeDisplay.call(this, player, options)); + + _this.on(player, 'durationchange', _this.throttledUpdateContent); + _this.on(player, 'ended', _this.handleEnded); + return _this; + } + + /** + * Builds the default DOM `className`. * - * @param {Component~ReadyCallback} [ready] - * Ready callback function. + * @return {string} + * The DOM `className` for this object. */ - function Player(tag, options, ready) { - _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 = (0, _obj.assign)(Player.getTagSettings(tag), options); + RemainingTimeDisplay.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-remaining-time'; + }; - // Delay the initialization of children because we need to set up - // player properties first, and can't use `this` before `super()` - options.initChildren = false; + /** + * The remaining time display prefixes numbers with a "minus" character. + * + * @param {number} time + * A numeric time, in seconds. + * + * @return {string} + * A formatted time + * + * @private + */ - // 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; + RemainingTimeDisplay.prototype.formatTime_ = function formatTime_(time) { + return '-' + _TimeDisplay.prototype.formatTime_.call(this, time); + }; - // If language is not set, get the closest lang attribute - if (!options.language) { - if (typeof tag.closest === 'function') { - var closest = tag.closest('[lang]'); + /** + * Update remaining time display. + * + * @param {EventTarget~Event} [event] + * The `timeupdate` or `durationchange` event that caused this to run. + * + * @listens Player#timeupdate + * @listens Player#durationchange + */ - if (closest) { - options.language = closest.getAttribute('lang'); - } - } else { - var element = tag; - while (element && element.nodeType === 1) { - if (Dom.getAttributes(element).hasOwnProperty('lang')) { - options.language = element.getAttribute('lang'); - break; - } - element = element.parentNode; - } - } + RemainingTimeDisplay.prototype.updateContent = function updateContent(event) { + if (!this.player_.duration()) { + return; } - // Run base component initializing with new options + // @deprecated We should only use remainingTimeDisplay + // as of video.js 7 + if (this.player_.remainingTimeDisplay) { + this.updateFormattedTime_(this.player_.remainingTimeDisplay()); + } else { + this.updateFormattedTime_(this.player_.remainingTime()); + } + }; - // Turn off API access because we're loading a new tech that might load asynchronously - var _this = _possibleConstructorReturn(this, _Component.call(this, null, options, ready)); + /** + * When the player fires ended there should be no time left. Sadly + * this is not always the case, lets make it seem like that is the case + * for users. + * + * @param {EventTarget~Event} [event] + * The `ended` event that caused this to run. + * + * @listens Player#ended + */ - _this.isReady_ = false; - // 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?'); + RemainingTimeDisplay.prototype.handleEnded = function handleEnded(event) { + if (!this.player_.duration()) { + return; } + this.updateFormattedTime_(0); + }; - // Store the original tag used to set options - _this.tag = tag; + return RemainingTimeDisplay; +}(TimeDisplay); - // Store the tag attributes used to restore html5 element - _this.tagAttributes = tag && Dom.getAttributes(tag); +/** + * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization. + * + * @type {string} + * @private + */ - // Update current language - _this.language(_this.options_.language); - // Update Supported Languages - if (options.languages) { - // Normalise player option languages to lowercase - var languagesToLower = {}; +RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time'; - 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_ = {}; +Component.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay); - // Set poster - _this.poster_ = options.poster || ''; +/** + * @file live-display.js + */ +// TODO - Future make it click to snap to live - // Set controls - _this.controls_ = !!options.controls; +/** + * Displays the live indicator when duration is Infinity. + * + * @extends Component + */ - // Set default values for lastVolume - _this.cache_.lastVolume = 1; +var LiveDisplay = function (_Component) { + inherits(LiveDisplay, _Component); - // 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; + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function LiveDisplay(player, options) { + classCallCheck(this, LiveDisplay); - /* - * Store the internal state of scrubbing - * - * @private - * @return {Boolean} True if the user is scrubbing - */ - _this.scrubbing_ = false; + var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); - _this.el_ = _this.createEl(); + _this.updateShowing(); + _this.on(_this.player(), 'durationchange', _this.updateShowing); + return _this; + } - // Make this an evented object and use `el_` as its event bus. - (0, _evented2['default'])(_this, { eventBusKey: 'el_' }); + /** + * Create the `Component`'s DOM element + * + * @return {Element} + * The element that was created. + */ - // 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 = (0, _mergeOptions2['default'])(_this.options_); - // Load plugins - if (options.plugins) { - var plugins = options.plugins; + LiveDisplay.prototype.createEl = function createEl$$1() { + var el = _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-live-control vjs-control' + }); - Object.keys(plugins).forEach(function (name) { - if (typeof this[name] === 'function') { - this[name](plugins[name]); - } else { - throw new Error('plugin "' + name + '" does not exist'); - } - }, _this); - } + this.contentEl_ = createEl('div', { + className: 'vjs-live-display', + innerHTML: '' + this.localize('Stream Type') + '' + this.localize('LIVE') + }, { + 'aria-live': 'off' + }); - _this.options_.playerOptions = playerOptionsCopy; + el.appendChild(this.contentEl_); + return el; + }; - _this.middleware_ = []; + LiveDisplay.prototype.dispose = function dispose() { + this.contentEl_ = null; - _this.initChildren(); + _Component.prototype.dispose.call(this); + }; - // Set isAudio based on whether or not an audio tag was used - _this.isAudio(tag.nodeName.toLowerCase() === 'audio'); + /** + * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide + * it accordingly + * + * @param {EventTarget~Event} [event] + * The {@link Player#durationchange} event that caused this function to run. + * + * @listens Player#durationchange + */ - // 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', _this.localize('Audio Player')); + LiveDisplay.prototype.updateShowing = function updateShowing(event) { + if (this.player().duration() === Infinity) { + this.show(); } else { - _this.el_.setAttribute('aria-label', _this.localize('Video Player')); - } - - if (_this.isAudio()) { - _this.addClass('vjs-audio'); + this.hide(); } + }; - if (_this.flexNotSupported_()) { - _this.addClass('vjs-no-flex'); - } + return LiveDisplay; +}(Component); - // 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'); - // } +Component.registerComponent('LiveDisplay', LiveDisplay); - // iOS Safari has broken hover handling - if (!browser.IS_IOS) { - _this.addClass('vjs-workinghover'); - } +/** + * @file slider.js + */ +/** + * The base functionality for a slider. Can be vertical or horizontal. + * For instance the volume bar or the seek bar on a video is a slider. + * + * @extends Component + */ - // Make player easily findable by ID - Player.players[_this.id_] = _this; +var Slider = function (_Component) { + inherits(Slider, _Component); - // Add a major version class to aid css in plugins - var majorVersion = '6.1.0'.split('.')[0]; + /** + * Create an instance of this class + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function Slider(player, options) { + classCallCheck(this, Slider); - _this.addClass('vjs-v' + majorVersion); + // Set property names to bar to match with the child Slider class is looking for + var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); - // 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.bar = _this.getChild(_this.options_.barName); - _this.on('fullscreenchange', _this.handleFullscreenChange_); - _this.on('stageclick', _this.handleStageClick_); + // Set a horizontal or vertical class on the slider depending on the slider type + _this.vertical(!!_this.options_.vertical); - _this.changingSrc_ = false; + _this.enable(); return _this; } /** - * Destroys the video player and does any necessary cleanup. - * - * This is especially helpful if you are dynamically adding and removing videos - * to/from the DOM. + * Are controls are currently enabled for this slider or not. * - * @fires Player#dispose + * @return {boolean} + * true if controls are enabled, false otherwise */ - Player.prototype.dispose = function dispose() { - /** - * Called when the player is being disposed of. - * - * @event Player#dispose - * @type {EventTarget~Event} - */ - this.trigger('dispose'); - // prevent dispose from being called twice - this.off('dispose'); + Slider.prototype.enabled = function enabled() { + return this.enabled_; + }; - if (this.styleEl_ && this.styleEl_.parentNode) { - this.styleEl_.parentNode.removeChild(this.styleEl_); - } + /** + * Enable controls for this slider if they are disabled + */ - // Kill reference to this player - Player.players[this.id_] = null; - if (this.tag && this.tag.player) { - this.tag.player = null; + Slider.prototype.enable = function enable() { + if (this.enabled()) { + return; } - if (this.el_ && this.el_.player) { - this.el_.player = null; + 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(this.player_, 'controlsvisible', this.update); + + if (this.playerEvent) { + this.on(this.player_, this.playerEvent, this.update); } - if (this.tech_) { - this.tech_.dispose(); + this.removeClass('disabled'); + this.setAttribute('tabindex', 0); + + this.enabled_ = true; + }; + + /** + * Disable controls for this slider if they are enabled + */ + + + Slider.prototype.disable = function disable() { + if (!this.enabled()) { + return; } + var doc = this.bar.el_.ownerDocument; - _Component.prototype.dispose.call(this); + this.off('mousedown', this.handleMouseDown); + this.off('touchstart', this.handleMouseDown); + this.off('focus', this.handleFocus); + this.off('blur', this.handleBlur); + this.off('click', this.handleClick); + this.off(this.player_, 'controlsvisible', this.update); + 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.removeAttribute('tabindex'); + + this.addClass('disabled'); + + if (this.playerEvent) { + this.off(this.player_, this.playerEvent, this.update); + } + this.enabled_ = false; }; /** - * Create the `Player`'s DOM element. + * Create the `Button`s DOM element. + * + * @param {string} type + * Type of element to create. + * + * @param {Object} [props={}] + * List of properties in Object form. + * + * @param {Object} [attributes={}] + * list of attributes in Object form. * * @return {Element} - * The DOM element that gets created. + * The element that gets created. */ - Player.prototype.createEl = function createEl() { - var tag = this.tag; - var el = void 0; - var playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player'); + Slider.prototype.createEl = function createEl$$1(type) { + var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - if (playerElIngest) { - el = this.el_ = tag.parentNode; - } else { - el = this.el_ = _Component.prototype.createEl.call(this, 'div'); - } + // Add the slider element class to all sub classes + props.className = props.className + ' vjs-slider'; + props = assign({ + tabIndex: 0 + }, props); - // set tabindex to -1 so we could focus on the player element - tag.setAttribute('tabindex', '-1'); + attributes = assign({ + 'role': 'slider', + 'aria-valuenow': 0, + 'aria-valuemin': 0, + 'aria-valuemax': 100, + 'tabIndex': 0 + }, attributes); - // Remove width/height attrs from tag so CSS can make it 100% width/height - tag.removeAttribute('width'); - tag.removeAttribute('height'); + return _Component.prototype.createEl.call(this, type, props, attributes); + }; - // 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.getAttributes(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 (_window2['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.addClass(linkEl, 'vjs-hidden'); - linkEl.setAttribute('hidden', 'hidden'); - } + /** + * Handle `mousedown` or `touchstart` events on the `Slider`. + * + * @param {EventTarget~Event} event + * `mousedown` or `touchstart` event that triggered this function + * + * @listens mousedown + * @listens touchstart + * @fires Slider#slideractive + */ - // 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 && !playerElIngest) { - tag.parentNode.insertBefore(el, tag); - } + Slider.prototype.handleMouseDown = function handleMouseDown(event) { + var doc = this.bar.el_.ownerDocument; - // 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 - // - // Breaks iPhone, fixed in HTML5 setup. - Dom.prependTo(tag, el); - this.children_.unshift(tag); + event.preventDefault(); + blockTextSelection(); - // Set lang attr on player to ensure CSS :lang() in consistent with player - // if it's been set to something different to the doc - this.el_.setAttribute('lang', this.language_); + this.addClass('vjs-sliding'); + /** + * Triggered when the slider is in an active state + * + * @event Slider#slideractive + * @type {EventTarget~Event} + */ + this.trigger('slideractive'); - this.el_ = el; + this.on(doc, 'mousemove', this.handleMouseMove); + this.on(doc, 'mouseup', this.handleMouseUp); + this.on(doc, 'touchmove', this.handleMouseMove); + this.on(doc, 'touchend', this.handleMouseUp); - return el; + this.handleMouseMove(event); }; /** - * A getter/setter for the `Player`'s width. + * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`. + * The `mousemove` and `touchmove` events will only only trigger this function during + * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and + * {@link Slider#handleMouseUp}. * - * @param {number} [value] - * The value to set the `Player's width to. + * @param {EventTarget~Event} event + * `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered + * this function * - * @return {number} - * The current width of the `Player` when getting. + * @listens mousemove + * @listens touchmove */ - Player.prototype.width = function width(value) { - return this.dimension('width', value); - }; + Slider.prototype.handleMouseMove = function handleMouseMove(event) {}; /** - * A getter/setter for the `Player`'s height. + * Handle `mouseup` or `touchend` events on the `Slider`. * - * @param {number} [value] - * The value to set the `Player's heigth to. + * @param {EventTarget~Event} event + * `mouseup` or `touchend` event that triggered this function. * - * @return {number} - * The current height of the `Player` when getting. + * @listens touchend + * @listens mouseup + * @fires Slider#sliderinactive */ - Player.prototype.height = function height(value) { - return this.dimension('height', value); + Slider.prototype.handleMouseUp = function handleMouseUp() { + var doc = this.bar.el_.ownerDocument; + + unblockTextSelection(); + + this.removeClass('vjs-sliding'); + /** + * Triggered when the slider is no longer in an active state. + * + * @event Slider#sliderinactive + * @type {EventTarget~Event} + */ + 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(); }; /** - * A getter/setter for the `Player`'s width & height. - * - * @param {string} dimension - * This string can be: - * - 'width' - * - 'height' - * - * @param {number} [value] - * Value for dimension specified in the first argument. + * Update the progress bar of the `Slider`. * - * @return {number} - * The dimension arguments value when getting (width/height). + * @returns {number} + * The percentage of progress the progress bar represents as a + * number from 0 to 1. */ - Player.prototype.dimension = function dimension(_dimension, value) { - var privDimension = _dimension + '_'; + Slider.prototype.update = function update() { - if (value === undefined) { - return this[privDimension] || 0; + // 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 (value === '') { - // If an empty string is given, reset the dimension to be automatic - this[privDimension] = undefined; - } else { - var parsedVal = parseFloat(value); + // 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 (isNaN(parsedVal)) { - _log2['default'].error('Improper value "' + value + '" supplied for for ' + _dimension); - return; - } + // If there's no bar... + if (!bar) { + return; + } - this[privDimension] = parsedVal; + // Protect against no duration and other division issues + if (typeof progress !== 'number' || progress !== progress || progress < 0 || progress === Infinity) { + progress = 0; } - this.updateStyleEl_(); + // Convert to a percentage for setting + var percentage = (progress * 100).toFixed(2) + '%'; + var style = bar.el().style; + + // Set the new bar width or height + if (this.vertical()) { + style.height = percentage; + } else { + style.width = percentage; + } + + return progress; }; /** - * A getter/setter/toggler for the vjs-fluid `className` on the `Player`. + * Calculate distance for slider * - * @param {boolean} [bool] - * - A value of true adds the class. - * - A value of false removes the class. - * - No value will toggle the fluid class. + * @param {EventTarget~Event} event + * The event that caused this function to run. * - * @return {boolean|undefined} - * - The value of fluid when getting. - * - `undefined` when setting. + * @return {number} + * The current position of the Slider. + * - postition.x for vertical `Slider`s + * - postition.y for horizontal `Slider`s */ - Player.prototype.fluid = function fluid(bool) { - if (bool === undefined) { - return !!this.fluid_; - } - - this.fluid_ = !!bool; + Slider.prototype.calculateDistance = function calculateDistance(event) { + var position = getPointerPosition(this.el_, event); - if (bool) { - this.addClass('vjs-fluid'); - } else { - this.removeClass('vjs-fluid'); + if (this.vertical()) { + return position.y; } - - this.updateStyleEl_(); + return position.x; }; /** - * Get/Set the aspect ratio + * Handle a `focus` event on this `Slider`. * - * @param {string} [ratio] - * Aspect ratio for player + * @param {EventTarget~Event} event + * The `focus` event that caused this function to run. * - * @return {string|undefined} - * returns the current aspect ratio when getting + * @listens focus */ + + Slider.prototype.handleFocus = function handleFocus() { + this.on(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress); + }; + /** - * A getter/setter for the `Player`'s aspect ratio. + * Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down + * arrow keys. This function will only be called when the slider has focus. See + * {@link Slider#handleFocus} and {@link Slider#handleBlur}. * - * @param {string} [ratio] - * The value to set the `Player's aspect ratio to. + * @param {EventTarget~Event} event + * the `keydown` event that caused this function to run. * - * @return {string|undefined} - * - The current aspect ratio of the `Player` when getting. - * - undefined when setting + * @listens keydown */ - 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.'); + Slider.prototype.handleKeyPress = function handleKeyPress(event) { + // Left and Down Arrows + if (event.which === 37 || event.which === 40) { + event.preventDefault(); + this.stepBack(); + + // Up and Right Arrows + } else if (event.which === 38 || event.which === 39) { + event.preventDefault(); + this.stepForward(); } - 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); + /** + * Handle a `blur` event on this `Slider`. + * + * @param {EventTarget~Event} event + * The `blur` event that caused this function to run. + * + * @listens blur + */ - this.updateStyleEl_(); + Slider.prototype.handleBlur = function handleBlur() { + this.off(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress); }; /** - * Update styles of the `Player` element (height, width and aspect ratio). + * Listener for click events on slider, used to prevent clicks + * from bubbling up to parent elements like button menus. * - * @private - * @listens Tech#loadedmetadata + * @param {Object} event + * Event that caused this object to run */ - Player.prototype.updateStyleEl_ = function updateStyleEl_() { - if (_window2['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; - } - } + Slider.prototype.handleClick = function handleClick(event) { + event.stopImmediatePropagation(); + event.preventDefault(); + }; - return; - } + /** + * 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, and getting + * - false if the slider is horizontal, and getting + */ - var width = void 0; - var height = void 0; - var aspectRatio = void 0; - var idClass = void 0; - // 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() > 0) { - // 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'; + Slider.prototype.vertical = function vertical(bool) { + if (bool === undefined) { + return this.vertical_ || false; } - // Get the ratio as a decimal we can use to calculate dimensions - var ratioParts = aspectRatio.split(':'); - var ratioMultiplier = ratioParts[1] / ratioParts[0]; + this.vertical_ = !!bool; - 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; + if (this.vertical_) { + this.addClass('vjs-slider-vertical'); } else { - // Or use the video's metadata, or use the video el's default of 300 - width = this.videoWidth() || 300; + this.addClass('vjs-slider-horizontal'); } + }; - 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; - } + return Slider; +}(Component); - // 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'; - } +Component.registerComponent('Slider', Slider); - // Ensure the right class is still on the player for the style element - this.addClass(idClass); +/** + * @file load-progress-bar.js + */ +/** + * Shows loading progress + * + * @extends Component + */ - 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 '); - }; +var LoadProgressBar = function (_Component) { + inherits(LoadProgressBar, _Component); /** - * Load/Create an instance of playback {@link Tech} including element - * and API methods. Then append the `Tech` element in `Player` as a child. + * Creates an instance of this class. * - * @param {string} techName - * name of the playback technology + * @param {Player} player + * The `Player` that this class should be attached to. * - * @param {string} source - * video source + * @param {Object} [options] + * The key/value store of player options. + */ + function LoadProgressBar(player, options) { + classCallCheck(this, LoadProgressBar); + + var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); + + _this.partEls_ = []; + _this.on(player, 'progress', _this.update); + return _this; + } + + /** + * Create the `Component`'s DOM element * - * @private + * @return {Element} + * The element that was created. */ - Player.prototype.loadTech_ = function loadTech_(techName, source) { - var _this2 = this; + LoadProgressBar.prototype.createEl = function createEl$$1() { + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-load-progress', + innerHTML: '' + this.localize('Loaded') + ': 0%' + }); + }; - // Pause and remove current playback technology - if (this.tech_) { - this.unloadTech_(); - } + LoadProgressBar.prototype.dispose = function dispose() { + this.partEls_ = null; - var titleTechName = (0, _toTitleCase2['default'])(techName); - var camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1); + _Component.prototype.dispose.call(this); + }; - // get rid of the HTML5 video tag as soon as we are using another tech - if (titleTechName !== 'Html5' && this.tag) { - _tech2['default'].getTech('Html5').disposeMediaElement(this.tag); - this.tag.player = null; - this.tag = null; - } + /** + * Update progress bar + * + * @param {EventTarget~Event} [event] + * The `progress` event that caused this function to run. + * + * @listens Player#progress + */ - this.techName_ = titleTechName; - // Turn off API access because we're loading a new tech that might load asynchronously - this.isReady_ = false; + LoadProgressBar.prototype.update = function update(event) { + var buffered = this.player_.buffered(); + var duration = this.player_.duration(); + var bufferedEnd = this.player_.bufferedEnd(); + var children = this.partEls_; - // Grab tech-specific options from player options and add source and parent element to use. - var techOptions = { - source: source, - 'nativeControlsForTouch': this.options_.nativeControlsForTouch, - 'playerId': this.id(), - 'techId': this.id() + '_' + titleTechName + '_api', - 'autoplay': this.options_.autoplay, - 'playsinline': this.options_.playsinline, - 'preload': this.options_.preload, - 'loop': this.options_.loop, - 'muted': this.options_.muted, - 'poster': this.poster(), - 'language': this.language(), - 'playerElIngest': this.playerElIngest_ || false, - 'vtt.js': this.options_['vtt.js'] + // get the percent width of a time compared to the total end + var percentify = function percentify(time, end) { + // no NaN + var percent = time / end || 0; + + return (percent >= 1 ? 1 : percent) * 100 + '%'; }; - _trackTypes.ALL.names.forEach(function (name) { - var props = _trackTypes.ALL[name]; + // update the width of the progress bar + this.el_.style.width = percentify(bufferedEnd, duration); - techOptions[props.getterName] = _this2[props.privateName]; - }); + // 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]; - (0, _obj.assign)(techOptions, this.options_[titleTechName]); - (0, _obj.assign)(techOptions, this.options_[camelTechName]); - (0, _obj.assign)(techOptions, this.options_[techName.toLowerCase()]); + if (!part) { + part = this.el_.appendChild(createEl()); + children[i] = part; + } - if (this.tag) { - techOptions.tag = this.tag; + // 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); } - if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) { - techOptions.startTime = this.cache_.currentTime; + // remove unused buffered range elements + for (var _i = children.length; _i > buffered.length; _i--) { + this.el_.removeChild(children[_i - 1]); } + children.length = buffered.length; + }; - // Initialize tech instance - var TechClass = _tech2['default'].getTech(techName); + return LoadProgressBar; +}(Component); - if (!TechClass) { - throw new Error('No Tech named \'' + titleTechName + '\' exists! \'' + titleTechName + '\' should be registered using videojs.registerTech()\''); - } +Component.registerComponent('LoadProgressBar', LoadProgressBar); - this.tech_ = new TechClass(techOptions); +/** + * @file time-tooltip.js + */ +/** + * Time tooltips display a time above the progress bar. + * + * @extends Component + */ - // player.triggerReady is always async, so don't need this to be async - this.tech_.ready(Fn.bind(this, this.handleTechReady_), true); +var TimeTooltip = function (_Component) { + inherits(TimeTooltip, _Component); - _textTrackListConverter2['default'].jsonToTextTracks(this.textTracksJson_ || [], this.tech_); + function TimeTooltip() { + classCallCheck(this, TimeTooltip); + return possibleConstructorReturn(this, _Component.apply(this, arguments)); + } - // Listen to all HTML5-defined events and trigger them on the player - TECH_EVENTS_RETRIGGER.forEach(function (event) { - _this2.on(_this2.tech_, event, _this2['handleTech' + (0, _toTitleCase2['default'])(event) + '_']); + /** + * Create the time tooltip DOM element + * + * @return {Element} + * The element that was created. + */ + TimeTooltip.prototype.createEl = function createEl$$1() { + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-time-tooltip' }); - 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_, 'durationchange', this.handleTechDurationChange_); - this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_); - this.on(this.tech_, 'error', this.handleTechError_); - this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_); - this.on(this.tech_, 'posterchange', this.handleTechPosterChange_); - this.on(this.tech_, 'textdata', this.handleTechTextData_); + }; - this.usingNativeControls(this.techGet_('controls')); + /** + * Updates the position of the time tooltip relative to the `SeekBar`. + * + * @param {Object} seekBarRect + * The `ClientRect` for the {@link SeekBar} element. + * + * @param {number} seekBarPoint + * A number from 0 to 1, representing a horizontal reference point + * from the left edge of the {@link SeekBar} + */ - 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() && (titleTechName !== 'Html5' || !this.tag)) { - Dom.prependTo(this.tech_.el(), this.el()); + TimeTooltip.prototype.update = function update(seekBarRect, seekBarPoint, content) { + var tooltipRect = getBoundingClientRect(this.el_); + var playerRect = getBoundingClientRect(this.player_.el()); + var seekBarPointPx = seekBarRect.width * seekBarPoint; + + // do nothing if either rect isn't available + // for example, if the player isn't in the DOM for testing + if (!playerRect || !tooltipRect) { + return; } - // Get rid of the original video tag reference after the first tech is loaded - if (this.tag) { - this.tag.player = null; - this.tag = null; + // This is the space left of the `seekBarPoint` available within the bounds + // of the player. We calculate any gap between the left edge of the player + // and the left edge of the `SeekBar` and add the number of pixels in the + // `SeekBar` before hitting the `seekBarPoint` + var spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx; + + // This is the space right of the `seekBarPoint` available within the bounds + // of the player. We calculate the number of pixels from the `seekBarPoint` + // to the right edge of the `SeekBar` and add to that any gap between the + // right edge of the `SeekBar` and the player. + var spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right); + + // This is the number of pixels by which the tooltip will need to be pulled + // further to the right to center it over the `seekBarPoint`. + var pullTooltipBy = tooltipRect.width / 2; + + // Adjust the `pullTooltipBy` distance to the left or right depending on + // the results of the space calculations above. + if (spaceLeftOfPoint < pullTooltipBy) { + pullTooltipBy += pullTooltipBy - spaceLeftOfPoint; + } else if (spaceRightOfPoint < pullTooltipBy) { + pullTooltipBy = spaceRightOfPoint; } - }; - /** - * Unload and dispose of the current playback {@link Tech}. - * - * @private - */ + // Due to the imprecision of decimal/ratio based calculations and varying + // rounding behaviors, there are cases where the spacing adjustment is off + // by a pixel or two. This adds insurance to these calculations. + if (pullTooltipBy < 0) { + pullTooltipBy = 0; + } else if (pullTooltipBy > tooltipRect.width) { + pullTooltipBy = tooltipRect.width; + } + this.el_.style.right = '-' + pullTooltipBy + 'px'; + textContent(this.el_, content); + }; - Player.prototype.unloadTech_ = function unloadTech_() { - var _this3 = this; + return TimeTooltip; +}(Component); - // Save the current text tracks so that we can reuse the same text tracks with the next tech - _trackTypes.ALL.names.forEach(function (name) { - var props = _trackTypes.ALL[name]; +Component.registerComponent('TimeTooltip', TimeTooltip); - _this3[props.privateName] = _this3[props.getterName](); - }); - this.textTracksJson_ = _textTrackListConverter2['default'].textTracksToJson(this.tech_); +/** + * @file play-progress-bar.js + */ +/** + * Used by {@link SeekBar} to display media playback progress as part of the + * {@link ProgressControl}. + * + * @extends Component + */ - this.isReady_ = false; +var PlayProgressBar = function (_Component) { + inherits(PlayProgressBar, _Component); - this.tech_.dispose(); + function PlayProgressBar() { + classCallCheck(this, PlayProgressBar); + return possibleConstructorReturn(this, _Component.apply(this, arguments)); + } - this.tech_ = false; + /** + * Create the the DOM element for this class. + * + * @return {Element} + * The element that was created. + */ + PlayProgressBar.prototype.createEl = function createEl() { + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-play-progress vjs-slider-bar', + innerHTML: '' + this.localize('Progress') + ': 0%' + }); }; /** - * Return a reference to the current {@link Tech}. - * It will print a warning by default about the danger of using the tech directly - * but any argument that is passed in will silence the warning. + * Enqueues updates to its own DOM as well as the DOM of its + * {@link TimeTooltip} child. * - * @param {*} [safety] - * Anything passed in to silence the warning + * @param {Object} seekBarRect + * The `ClientRect` for the {@link SeekBar} element. * - * @return {Tech} - * The Tech + * @param {number} seekBarPoint + * A number from 0 to 1, representing a horizontal reference point + * from the left edge of the {@link SeekBar} */ - Player.prototype.tech = function tech(safety) { - if (safety === undefined) { - _log2['default'].warn((0, _tsml2['default'])(_templateObject)); + PlayProgressBar.prototype.update = function update(seekBarRect, seekBarPoint) { + var _this2 = this; + + // If there is an existing rAF ID, cancel it so we don't over-queue. + if (this.rafId_) { + this.cancelAnimationFrame(this.rafId_); } - return this.tech_; + this.rafId_ = this.requestAnimationFrame(function () { + var time = _this2.player_.scrubbing() ? _this2.player_.getCache().currentTime : _this2.player_.currentTime(); + + var content = formatTime(time, _this2.player_.duration()); + var timeTooltip = _this2.getChild('timeTooltip'); + + if (timeTooltip) { + timeTooltip.update(seekBarRect, seekBarPoint, content); + } + }); }; + return PlayProgressBar; +}(Component); + +/** + * Default options for {@link PlayProgressBar}. + * + * @type {Object} + * @private + */ + + +PlayProgressBar.prototype.options_ = { + children: [] +}; + +// Time tooltips should not be added to a player on mobile devices or IE8 +if ((!IE_VERSION || IE_VERSION > 8) && !IS_IOS && !IS_ANDROID) { + PlayProgressBar.prototype.options_.children.push('timeTooltip'); +} + +Component.registerComponent('PlayProgressBar', PlayProgressBar); + +/** + * @file mouse-time-display.js + */ +/** + * The {@link MouseTimeDisplay} component tracks mouse movement over the + * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip} + * indicating the time which is represented by a given point in the + * {@link ProgressControl}. + * + * @extends Component + */ + +var MouseTimeDisplay = function (_Component) { + inherits(MouseTimeDisplay, _Component); + /** - * Set up click and touch listeners for the playback element - * - * - On desktops: a click on the video itself will toggle playback - * - On mobile devices: a click on the video toggles controls - * which 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. + * Creates an instance of this class. * - * > 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 + * @param {Player} player + * The {@link Player} that this class should be attached to. * - * @private + * @param {Object} [options] + * The key/value store of player options. */ + function MouseTimeDisplay(player, options) { + classCallCheck(this, MouseTimeDisplay); + var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); - 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_); - }; + _this.update = throttle(bind(_this, _this.update), 25); + return _this; + } /** - * Remove the listeners used for click and tap controls. This is needed for - * toggling to controls disabled, where a tap/touch should do nothing. + * Create the DOM element for this class. * - * @private + * @return {Element} + * The element that was created. */ - 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_); + MouseTimeDisplay.prototype.createEl = function createEl() { + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-mouse-display' + }); }; /** - * Player waits for the tech to be ready + * Enqueues updates to its own DOM as well as the DOM of its + * {@link TimeTooltip} child. * - * @private + * @param {Object} seekBarRect + * The `ClientRect` for the {@link SeekBar} element. + * + * @param {number} seekBarPoint + * A number from 0 to 1, representing a horizontal reference point + * from the left edge of the {@link SeekBar} */ - Player.prototype.handleTechReady_ = function handleTechReady_() { - this.triggerReady(); + MouseTimeDisplay.prototype.update = function update(seekBarRect, seekBarPoint) { + var _this2 = this; - // Keep the same volume as before - if (this.cache_.volume) { - this.techCall_('setVolume', this.cache_.volume); + // If there is an existing rAF ID, cancel it so we don't over-queue. + if (this.rafId_) { + this.cancelAnimationFrame(this.rafId_); } - // Look if the tech found a higher resolution poster while loading - this.handleTechPosterChange_(); - - // Update the duration if available - this.handleTechDurationChange_(); + this.rafId_ = this.requestAnimationFrame(function () { + var duration = _this2.player_.duration(); + var content = formatTime(seekBarPoint * duration, duration); - // 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.currentSrc()) && this.tag && this.options_.autoplay && this.paused()) { - try { - // Chrome Fix. Fixed in Chrome v16. - delete this.tag.poster; - } catch (e) { - (0, _log2['default'])('deleting tag.poster throws in some browsers', e); - } - this.play(); - } + _this2.el_.style.left = seekBarRect.width * seekBarPoint + 'px'; + _this2.getChild('timeTooltip').update(seekBarRect, seekBarPoint, content); + }); }; - /** - * Retrigger the `loadstart` event that was triggered by the {@link Tech}. This - * function will also trigger {@link Player#firstplay} if it is the first loadstart - * for a video. - * - * @fires Player#loadstart - * @fires Player#firstplay - * @listens Tech#loadstart - * @private - */ + return MouseTimeDisplay; +}(Component); +/** + * Default options for `MouseTimeDisplay` + * + * @type {Object} + * @private + */ - Player.prototype.handleTechLoadStart_ = function handleTechLoadStart_() { - // TODO: Update to use `emptied` event instead. See #1277. - this.removeClass('vjs-ended'); - this.removeClass('vjs-seeking'); +MouseTimeDisplay.prototype.options_ = { + children: ['timeTooltip'] +}; - // reset the error state - this.error(null); +Component.registerComponent('MouseTimeDisplay', MouseTimeDisplay); - // 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()) { - /** - * Fired when the user agent begins looking for media data - * - * @event Player#loadstart - * @type {EventTarget~Event} - */ - this.trigger('loadstart'); - this.trigger('firstplay'); - } else { - // reset the hasStarted state - this.hasStarted(false); - this.trigger('loadstart'); - } - }; +/** + * @file seek-bar.js + */ +// The number of seconds the `step*` functions move the timeline. +var STEP_SECONDS = 5; + +// The interval at which the bar should update as it progresses. +var UPDATE_REFRESH_INTERVAL = 30; + +/** + * Seek bar and container for the progress bars. Uses {@link PlayProgressBar} + * as its `bar`. + * + * @extends Slider + */ + +var SeekBar = function (_Slider) { + inherits(SeekBar, _Slider); /** - * Add/remove the vjs-has-started class - * - * @fires Player#firstplay + * Creates an instance of this class. * - * @param {boolean} hasStarted - * - true: adds the class - * - false: remove the class + * @param {Player} player + * The `Player` that this class should be attached to. * - * @return {boolean} - * the boolean value of hasStarted + * @param {Object} [options] + * The key/value store of player options. */ + function SeekBar(player, options) { + classCallCheck(this, SeekBar); + var _this = possibleConstructorReturn(this, _Slider.call(this, player, options)); - 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; - } - return !!this.hasStarted_; - }; + _this.update = throttle(bind(_this, _this.update), UPDATE_REFRESH_INTERVAL); - /** - * Fired whenever the media begins or resumes playback - * - * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play} - * @fires Player#play - * @listens Tech#play - * @private - */ + _this.on(player, 'timeupdate', _this.update); + _this.on(player, 'ended', _this.handleEnded); + // when playing, let's ensure we smoothly update the play progress bar + // via an interval + _this.updateInterval = null; - Player.prototype.handleTechPlay_ = function handleTechPlay_() { - this.removeClass('vjs-ended'); - this.removeClass('vjs-paused'); - this.addClass('vjs-playing'); + _this.on(player, ['playing'], function () { + _this.clearInterval(_this.updateInterval); - // hide the poster when the user hits play - this.hasStarted(true); - /** - * Triggered whenever an {@link Tech#play} event happens. Indicates that - * playback has started or resumed. - * - * @event Player#play - * @type {EventTarget~Event} - */ - this.trigger('play'); - }; + _this.updateInterval = _this.setInterval(function () { + _this.requestAnimationFrame(function () { + _this.update(); + }); + }, UPDATE_REFRESH_INTERVAL); + }); + + _this.on(player, ['ended', 'pause', 'waiting'], function () { + _this.clearInterval(_this.updateInterval); + }); + + _this.on(player, ['timeupdate', 'ended'], _this.update); + return _this; + } /** - * Retrigger the `waiting` event that was triggered by the {@link Tech}. + * Create the `Component`'s DOM element * - * @fires Player#waiting - * @listens Tech#waiting - * @private + * @return {Element} + * The element that was created. */ - Player.prototype.handleTechWaiting_ = function handleTechWaiting_() { - var _this4 = this; - - this.addClass('vjs-waiting'); - /** - * A readyState change on the DOM element has caused playback to stop. - * - * @event Player#waiting - * @type {EventTarget~Event} - */ - this.trigger('waiting'); - this.one('timeupdate', function () { - return _this4.removeClass('vjs-waiting'); + SeekBar.prototype.createEl = function createEl$$1() { + return _Slider.prototype.createEl.call(this, 'div', { + className: 'vjs-progress-holder' + }, { + 'aria-label': this.localize('Progress Bar') }); }; /** - * Retrigger the `canplay` event that was triggered by the {@link Tech}. - * > Note: This is not consistent between browsers. See #1351 + * This function updates the play progress bar and accessiblity + * attributes to whatever is passed in. + * + * @param {number} currentTime + * The currentTime value that should be used for accessiblity + * + * @param {number} percent + * The percentage as a decimal that the bar should be filled from 0-1. * - * @fires Player#canplay - * @listens Tech#canplay * @private */ - Player.prototype.handleTechCanPlay_ = function handleTechCanPlay_() { - this.removeClass('vjs-waiting'); - /** - * The media has a readyState of HAVE_FUTURE_DATA or greater. - * - * @event Player#canplay - * @type {EventTarget~Event} - */ - this.trigger('canplay'); + SeekBar.prototype.update_ = function update_(currentTime, percent) { + var duration = this.player_.duration(); + + // machine readable value of progress bar (percentage complete) + this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2)); + + // human readable value of progress bar (time complete) + this.el_.setAttribute('aria-valuetext', this.localize('progress bar timing: currentTime={1} duration={2}', [formatTime(currentTime, duration), formatTime(duration, duration)], '{1} of {2}')); + + // Update the `PlayProgressBar`. + this.bar.update(getBoundingClientRect(this.el_), percent); }; /** - * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}. + * Update the seek bar's UI. * - * @fires Player#canplaythrough - * @listens Tech#canplaythrough - * @private + * @param {EventTarget~Event} [event] + * The `timeupdate` or `ended` event that caused this to run. + * + * @listens Player#timeupdate + * + * @returns {number} + * The current percent at a number from 0-1 */ - Player.prototype.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() { - this.removeClass('vjs-waiting'); - /** - * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the - * entire media file can be played without buffering. - * - * @event Player#canplaythrough - * @type {EventTarget~Event} - */ - this.trigger('canplaythrough'); + SeekBar.prototype.update = function update(event) { + var percent = _Slider.prototype.update.call(this); + + this.update_(this.getCurrentTime_(), percent); + return percent; }; /** - * Retrigger the `playing` event that was triggered by the {@link Tech}. + * Get the value of current time but allows for smooth scrubbing, + * when player can't keep up. + * + * @return {number} + * The current time value to display * - * @fires Player#playing - * @listens Tech#playing * @private */ - Player.prototype.handleTechPlaying_ = function handleTechPlaying_() { - this.removeClass('vjs-waiting'); - /** - * The media is no longer blocked from playback, and has started playing. - * - * @event Player#playing - * @type {EventTarget~Event} - */ - this.trigger('playing'); + SeekBar.prototype.getCurrentTime_ = function getCurrentTime_() { + return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); }; /** - * Retrigger the `seeking` event that was triggered by the {@link Tech}. + * We want the seek bar to be full on ended + * no matter what the actual internal values are. so we force it. * - * @fires Player#seeking - * @listens Tech#seeking - * @private + * @param {EventTarget~Event} [event] + * The `timeupdate` or `ended` event that caused this to run. + * + * @listens Player#ended */ - Player.prototype.handleTechSeeking_ = function handleTechSeeking_() { - this.addClass('vjs-seeking'); - /** - * Fired whenever the player is jumping to a new time - * - * @event Player#seeking - * @type {EventTarget~Event} - */ - this.trigger('seeking'); + SeekBar.prototype.handleEnded = function handleEnded(event) { + this.update_(this.player_.duration(), 1); }; /** - * Retrigger the `seeked` event that was triggered by the {@link Tech}. + * Get the percentage of media played so far. * - * @fires Player#seeked - * @listens Tech#seeked - * @private + * @return {number} + * The percentage of media played so far (0 to 1). */ - Player.prototype.handleTechSeeked_ = function handleTechSeeked_() { - this.removeClass('vjs-seeking'); - /** - * Fired when the player has finished jumping to a new time - * - * @event Player#seeked - * @type {EventTarget~Event} - */ - this.trigger('seeked'); + SeekBar.prototype.getPercent = function getPercent() { + var percent = this.getCurrentTime_() / this.player_.duration(); + + return percent >= 1 ? 1 : percent; }; /** - * Retrigger the `firstplay` event that was triggered by the {@link Tech}. + * Handle mouse down on seek bar * - * @fires Player#firstplay - * @listens Tech#firstplay - * @deprecated As of 6.0 firstplay event is deprecated. - * @deprecated As of 6.0 passing the `starttime` option to the player and the firstplay event are deprecated. - * @private + * @param {EventTarget~Event} event + * The `mousedown` event that caused this to run. + * + * @listens mousedown */ - 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) { - _log2['default'].warn('Passing the `starttime` option to the player will be deprecated in 6.0'); - this.currentTime(this.options_.starttime); + SeekBar.prototype.handleMouseDown = function handleMouseDown(event) { + if (!isSingleLeftClick(event)) { + return; } - this.addClass('vjs-has-started'); - /** - * Fired the first time a video is played. Not part of the HLS spec, and this is - * probably not the best implementation yet, so use sparingly. If you don't have a - * reason to prevent playback, use `myPlayer.one('play');` instead. - * - * @event Player#firstplay - * @deprecated As of 6.0 firstplay event is deprecated. - * @type {EventTarget~Event} - */ - this.trigger('firstplay'); + // Stop event propagation to prevent double fire in progress-control.js + event.stopPropagation(); + this.player_.scrubbing(true); + + this.videoWasPlaying = !this.player_.paused(); + this.player_.pause(); + + _Slider.prototype.handleMouseDown.call(this, event); }; /** - * Retrigger the `pause` event that was triggered by the {@link Tech}. + * Handle mouse move on seek bar * - * @fires Player#pause - * @listens Tech#pause - * @private + * @param {EventTarget~Event} event + * The `mousemove` event that caused this to run. + * + * @listens mousemove */ - Player.prototype.handleTechPause_ = function handleTechPause_() { - this.removeClass('vjs-playing'); - this.addClass('vjs-paused'); - /** - * Fired whenever the media has been paused - * - * @event Player#pause - * @type {EventTarget~Event} - */ - this.trigger('pause'); - }; + SeekBar.prototype.handleMouseMove = function handleMouseMove(event) { + if (!isSingleLeftClick(event)) { + return; + } - /** - * Retrigger the `ended` event that was triggered by the {@link Tech}. - * - * @fires Player#ended - * @listens Tech#ended - * @private - */ + var newTime = this.calculateDistance(event) * this.player_.duration(); + // Don't let video end while scrubbing. + if (newTime === this.player_.duration()) { + newTime = newTime - 0.1; + } - Player.prototype.handleTechEnded_ = function handleTechEnded_() { - this.addClass('vjs-ended'); - if (this.options_.loop) { - this.currentTime(0); - this.play(); - } else if (!this.paused()) { - this.pause(); + // Set new time (tell player to seek to new time) + this.player_.currentTime(newTime); + }; + + SeekBar.prototype.enable = function enable() { + _Slider.prototype.enable.call(this); + var mouseTimeDisplay = this.getChild('mouseTimeDisplay'); + + if (!mouseTimeDisplay) { + return; } - /** - * Fired when the end of the media resource is reached (currentTime == duration) - * - * @event Player#ended - * @type {EventTarget~Event} - */ - this.trigger('ended'); + mouseTimeDisplay.show(); }; - /** - * Fired when the duration of the media resource is first known or changed - * - * @listens Tech#durationchange - * @private - */ + SeekBar.prototype.disable = function disable() { + _Slider.prototype.disable.call(this); + var mouseTimeDisplay = this.getChild('mouseTimeDisplay'); + if (!mouseTimeDisplay) { + return; + } - Player.prototype.handleTechDurationChange_ = function handleTechDurationChange_() { - this.duration(this.techGet_('duration')); + mouseTimeDisplay.hide(); }; /** - * Handle a click on the media element to play/pause + * Handle mouse up on seek bar * * @param {EventTarget~Event} event - * the event that caused this function to trigger + * The `mouseup` event that caused this to run. * - * @listens Tech#mousedown - * @private + * @listens mouseup */ - 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; + SeekBar.prototype.handleMouseUp = function handleMouseUp(event) { + _Slider.prototype.handleMouseUp.call(this, event); + + // Stop event propagation to prevent double fire in progress-control.js + if (event) { + event.stopPropagation(); } + this.player_.scrubbing(false); - // 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(); - } + /** + * Trigger timeupdate because we're done seeking and the time has changed. + * This is particularly useful for if the player is paused to time the time displays. + * + * @event Tech#timeupdate + * @type {EventTarget~Event} + */ + this.player_.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); + if (this.videoWasPlaying) { + silencePromise(this.player_.play()); } }; /** - * Handle a tap on the media element. It will toggle the user - * activity state, which hides and shows the controls. - * - * @listens Tech#tap - * @private + * Move more quickly fast forward for keyboard-only users */ - Player.prototype.handleTechTap_ = function handleTechTap_() { - this.userActive(!this.userActive()); + SeekBar.prototype.stepForward = function stepForward() { + this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS); }; /** - * Handle touch to start - * - * @listens Tech#touchstart - * @private + * Move more quickly rewind for keyboard-only users */ - Player.prototype.handleTechTouchStart_ = function handleTechTouchStart_() { - this.userWasActive = this.userActive(); + SeekBar.prototype.stepBack = function stepBack() { + this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS); }; /** - * Handle touch to move + * Toggles the playback state of the player + * This gets called when enter or space is used on the seekbar + * + * @param {EventTarget~Event} event + * The `keydown` event that caused this function to be called * - * @listens Tech#touchmove - * @private */ - Player.prototype.handleTechTouchMove_ = function handleTechTouchMove_() { - if (this.userWasActive) { - this.reportUserActivity(); + SeekBar.prototype.handleAction = function handleAction(event) { + if (this.player_.paused()) { + this.player_.play(); + } else { + this.player_.pause(); } }; /** - * Handle touch to end + * Called when this SeekBar has focus and a key gets pressed down. By + * default it will call `this.handleAction` when the key is space or enter. * * @param {EventTarget~Event} event - * the touchend event that triggered - * this function + * The `keydown` event that caused this function to be called. * - * @listens Tech#touchend - * @private + * @listens keydown */ - Player.prototype.handleTechTouchEnd_ = function handleTechTouchEnd_(event) { - // Stop the mouse events from also happening - event.preventDefault(); + SeekBar.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.handleAction(event); + } else if (_Slider.prototype.handleKeyPress) { + + // Pass keypress handling up for unsupported keys + _Slider.prototype.handleKeyPress.call(this, event); + } }; + return SeekBar; +}(Slider); + +/** + * Default options for the `SeekBar` + * + * @type {Object} + * @private + */ + + +SeekBar.prototype.options_ = { + children: ['loadProgressBar', 'playProgressBar'], + barName: 'playProgressBar' +}; + +// MouseTimeDisplay tooltips should not be added to a player on mobile devices or IE8 +if ((!IE_VERSION || IE_VERSION > 8) && !IS_IOS && !IS_ANDROID) { + SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay'); +} + +/** + * Call the update event for this Slider when this event happens on the player. + * + * @type {string} + */ +SeekBar.prototype.playerEvent = 'timeupdate'; + +Component.registerComponent('SeekBar', SeekBar); + +/** + * @file progress-control.js + */ +/** + * The Progress Control component contains the seek bar, load progress, + * and play progress. + * + * @extends Component + */ + +var ProgressControl = function (_Component) { + inherits(ProgressControl, _Component); + /** - * Fired when the player switches in or out of fullscreen mode + * Creates an instance of this class. * - * @private - * @listens Player#fullscreenchange + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. */ + function ProgressControl(player, options) { + classCallCheck(this, ProgressControl); + var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); - Player.prototype.handleFullscreenChange_ = function handleFullscreenChange_() { - if (this.isFullscreen()) { - this.addClass('vjs-fullscreen'); - } else { - this.removeClass('vjs-fullscreen'); - } - }; + _this.handleMouseMove = throttle(bind(_this, _this.handleMouseMove), 25); + _this.throttledHandleMouseSeek = throttle(bind(_this, _this.handleMouseSeek), 25); + + _this.enable(); + return _this; + } /** - * native click events on the SWF aren't triggered on IE11, Win8.1RT - * use stageclick events triggered from inside the SWF instead + * Create the `Component`'s DOM element * - * @private - * @listens stageclick + * @return {Element} + * The element that was created. */ - Player.prototype.handleStageClick_ = function handleStageClick_() { - this.reportUserActivity(); + ProgressControl.prototype.createEl = function createEl$$1() { + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-progress-control vjs-control' + }); }; /** - * Handle Tech Fullscreen Change + * When the mouse moves over the `ProgressControl`, the pointer position + * gets passed down to the `MouseTimeDisplay` component. * * @param {EventTarget~Event} event - * the fullscreenchange event that triggered this function - * - * @param {Object} data - * the data that was sent with the event + * The `mousemove` event that caused this function to run. * - * @private - * @listens Tech#fullscreenchange - * @fires Player#fullscreenchange + * @listen mousemove */ - Player.prototype.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) { - if (data) { - this.isFullscreen(data.isFullscreen); + ProgressControl.prototype.handleMouseMove = function handleMouseMove(event) { + var seekBar = this.getChild('seekBar'); + + if (seekBar) { + var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay'); + var seekBarEl = seekBar.el(); + var seekBarRect = getBoundingClientRect(seekBarEl); + var seekBarPoint = getPointerPosition(seekBarEl, event).x; + + // The default skin has a gap on either side of the `SeekBar`. This means + // that it's possible to trigger this behavior outside the boundaries of + // the `SeekBar`. This ensures we stay within it at all times. + if (seekBarPoint > 1) { + seekBarPoint = 1; + } else if (seekBarPoint < 0) { + seekBarPoint = 0; + } + + if (mouseTimeDisplay) { + mouseTimeDisplay.update(seekBarRect, seekBarPoint); + } } - /** - * Fired when going in and out of fullscreen. - * - * @event Player#fullscreenchange - * @type {EventTarget~Event} - */ - this.trigger('fullscreenchange'); }; /** - * Fires when an error occurred during the loading of an audio/video. + * A throttled version of the {@link ProgressControl#handleMouseSeek} listener. * - * @private - * @listens Tech#error + * @method ProgressControl#throttledHandleMouseSeek + * @param {EventTarget~Event} event + * The `mousemove` event that caused this function to run. + * + * @listen mousemove + * @listen touchmove */ - - Player.prototype.handleTechError_ = function handleTechError_() { - var error = this.tech_.error(); - - this.error(error); - }; - /** - * Retrigger the `textdata` event that was triggered by the {@link Tech}. + * Handle `mousemove` or `touchmove` events on the `ProgressControl`. * - * @fires Player#textdata - * @listens Tech#textdata - * @private + * @param {EventTarget~Event} event + * `mousedown` or `touchstart` event that triggered this function + * + * @listens mousemove + * @listens touchmove */ - Player.prototype.handleTechTextData_ = function handleTechTextData_() { - var data = null; + ProgressControl.prototype.handleMouseSeek = function handleMouseSeek(event) { + var seekBar = this.getChild('seekBar'); - if (arguments.length > 1) { - data = arguments[1]; + if (seekBar) { + seekBar.handleMouseMove(event); } - - /** - * Fires when we get a textdata event from tech - * - * @event Player#textdata - * @type {EventTarget~Event} - */ - this.trigger('textdata', data); }; /** - * Get object for cached values. + * Are controls are currently enabled for this progress control. * - * @return {Object} - * get the current object cache + * @return {boolean} + * true if controls are enabled, false otherwise */ - Player.prototype.getCache = function getCache() { - return this.cache_; + ProgressControl.prototype.enabled = function enabled() { + return this.enabled_; }; /** - * Pass values to the playback tech - * - * @param {string} [method] - * the method to call - * - * @param {Object} arg - * the argument to pass - * - * @private + * Disable all controls on the progress control and its children */ - Player.prototype.techCall_ = function techCall_(method, arg) { - // If it's not ready yet, call method when it is + ProgressControl.prototype.disable = function disable() { + this.children().forEach(function (child) { + return child.disable && child.disable(); + }); - this.ready(function () { - if (method in middleware.allowedSetters) { - return middleware.set(this.middleware_, this.tech_, method, arg); - } + if (!this.enabled()) { + return; + } - try { - if (this.tech_) { - this.tech_[method](arg); - } - } catch (e) { - (0, _log2['default'])(e); - throw e; - } - }, true); + this.off(['mousedown', 'touchstart'], this.handleMouseDown); + this.off(this.el_, 'mousemove', this.handleMouseMove); + this.handleMouseUp(); + + this.addClass('disabled'); + + this.enabled_ = false; }; /** - * Get calls can't wait for the tech, and sometimes don't need to. - * - * @param {string} method - * Tech method - * - * @return {Function|undefined} - * the method or undefined - * - * @private + * Enable all controls on the progress control and its children */ - Player.prototype.techGet_ = function techGet_(method) { - if (this.tech_ && this.tech_.isReady_) { - - if (method in middleware.allowedGetters) { - return middleware.get(this.middleware_, this.tech_, method); - } + ProgressControl.prototype.enable = function enable() { + this.children().forEach(function (child) { + return child.enable && child.enable(); + }); - // 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) { - (0, _log2['default'])('Video.js: ' + method + ' method not defined for ' + this.techName_ + ' playback technology.', e); - - // When a method isn't available on the object it throws a TypeError - } else if (e.name === 'TypeError') { - (0, _log2['default'])('Video.js: ' + method + ' unavailable on ' + this.techName_ + ' playback technology element.', e); - this.tech_.isReady_ = false; - } else { - (0, _log2['default'])(e); - } - throw e; - } + if (this.enabled()) { + return; } - return; + this.on(['mousedown', 'touchstart'], this.handleMouseDown); + this.on(this.el_, 'mousemove', this.handleMouseMove); + this.removeClass('disabled'); + + this.enabled_ = true; }; /** - * start media playback + * Handle `mousedown` or `touchstart` events on the `ProgressControl`. * - * @return {Promise|undefined} - * Returns a `Promise` if the browser returns one, for most browsers this will - * return undefined. + * @param {EventTarget~Event} event + * `mousedown` or `touchstart` event that triggered this function + * + * @listens mousedown + * @listens touchstart */ - Player.prototype.play = function play() { - if (this.changingSrc_) { - this.ready(function () { - var retval = this.techGet_('play'); - - // silence errors (unhandled promise from play) - if (retval !== undefined && typeof retval.then === 'function') { - retval.then(null, function (e) {}); - } - }); - - // Only calls the tech's play if we already have a src loaded - } else if (this.isReady_ && (this.src() || this.currentSrc())) { - return this.techGet_('play'); - } else { - this.ready(function () { - this.tech_.one('loadstart', function () { - var retval = this.play(); + ProgressControl.prototype.handleMouseDown = function handleMouseDown(event) { + var doc = this.el_.ownerDocument; + var seekBar = this.getChild('seekBar'); - // silence errors (unhandled promise from play) - if (retval !== undefined && typeof retval.then === 'function') { - retval.then(null, function (e) {}); - } - }); - }); + if (seekBar) { + seekBar.handleMouseDown(event); } + + this.on(doc, 'mousemove', this.throttledHandleMouseSeek); + this.on(doc, 'touchmove', this.throttledHandleMouseSeek); + this.on(doc, 'mouseup', this.handleMouseUp); + this.on(doc, 'touchend', this.handleMouseUp); }; /** - * Pause the video playback + * Handle `mouseup` or `touchend` events on the `ProgressControl`. * - * @return {Player} - * A reference to the player object this function was called on + * @param {EventTarget~Event} event + * `mouseup` or `touchend` event that triggered this function. + * + * @listens touchend + * @listens mouseup */ - Player.prototype.pause = function pause() { - this.techCall_('pause'); + ProgressControl.prototype.handleMouseUp = function handleMouseUp(event) { + var doc = this.el_.ownerDocument; + var seekBar = this.getChild('seekBar'); + + if (seekBar) { + seekBar.handleMouseUp(event); + } + + this.off(doc, 'mousemove', this.throttledHandleMouseSeek); + this.off(doc, 'touchmove', this.throttledHandleMouseSeek); + this.off(doc, 'mouseup', this.handleMouseUp); + this.off(doc, 'touchend', this.handleMouseUp); }; + return ProgressControl; +}(Component); + +/** + * Default options for `ProgressControl` + * + * @type {Object} + * @private + */ + + +ProgressControl.prototype.options_ = { + children: ['seekBar'] +}; + +Component.registerComponent('ProgressControl', ProgressControl); + +/** + * @file fullscreen-toggle.js + */ +/** + * Toggle fullscreen video + * + * @extends Button + */ + +var FullscreenToggle = function (_Button) { + inherits(FullscreenToggle, _Button); + /** - * Check if the player is paused or has yet to play + * Creates an instance of this class. * - * @return {boolean} - * - false: if the media is currently playing - * - true: if media is not currently playing + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. */ + function FullscreenToggle(player, options) { + classCallCheck(this, FullscreenToggle); + var _this = possibleConstructorReturn(this, _Button.call(this, player, options)); - 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; - }; + _this.on(player, 'fullscreenchange', _this.handleFullscreenChange); + return _this; + } /** - * Get a TimeRange object representing the current ranges of time that the user - * has played. + * Builds the default DOM `className`. * - * @return {TimeRange} - * A time range object that represents all the increments of time that have - * been played. + * @return {string} + * The DOM `className` for this object. */ - Player.prototype.played = function played() { - return this.techGet_('played') || (0, _timeRanges.createTimeRange)(0, 0); + FullscreenToggle.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-fullscreen-control ' + _Button.prototype.buildCSSClass.call(this); }; /** - * 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. + * Handles fullscreenchange on the player and change control text accordingly. * - * @param {boolean} [isScrubbing] - * wether the user is or is not scrubbing + * @param {EventTarget~Event} [event] + * The {@link Player#fullscreenchange} event that caused this function to be + * called. * - * @return {boolean} - * The value of scrubbing when getting + * @listens Player#fullscreenchange */ - Player.prototype.scrubbing = function scrubbing(isScrubbing) { - if (typeof isScrubbing === 'undefined') { - return this.scrubbing_; - } - this.scrubbing_ = !!isScrubbing; - - if (isScrubbing) { - this.addClass('vjs-scrubbing'); + FullscreenToggle.prototype.handleFullscreenChange = function handleFullscreenChange(event) { + if (this.player_.isFullscreen()) { + this.controlText('Non-Fullscreen'); } else { - this.removeClass('vjs-scrubbing'); + this.controlText('Fullscreen'); } }; /** - * Get or set the current time (in seconds) + * This gets called when an `FullscreenToggle` is "clicked". See + * {@link ClickableComponent} for more detailed information on what a click can be. * - * @param {number|string} [seconds] - * The time to seek to in seconds + * @param {EventTarget~Event} [event] + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. * - * @return {number} - * - the current time in seconds when getting + * @listens tap + * @listens click */ - Player.prototype.currentTime = function currentTime(seconds) { - if (typeof seconds !== 'undefined') { - this.techCall_('setCurrentTime', seconds); - return; + FullscreenToggle.prototype.handleClick = function handleClick(event) { + if (!this.player_.isFullscreen()) { + this.player_.requestFullscreen(); + } else { + this.player_.exitFullscreen(); } - - // 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. - this.cache_.currentTime = this.techGet_('currentTime') || 0; - return this.cache_.currentTime; }; - /** - * Normally gets the length in time of the video in seconds; - * in all but the rarest use cases an argument will NOT be passed to the method - * - * > **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. - * - * @fires Player#durationchange - * - * @param {number} [seconds] - * The duration of the video to set in seconds - * - * @return {number} - * - The duration of the video in seconds when getting - */ + return FullscreenToggle; +}(Button); +/** + * The text that should display over the `FullscreenToggle`s controls. Added for localization. + * + * @type {string} + * @private + */ - Player.prototype.duration = function duration(seconds) { - if (seconds === undefined) { - return this.cache_.duration || 0; - } - seconds = parseFloat(seconds) || 0; +FullscreenToggle.prototype.controlText_ = 'Fullscreen'; - // Standardize on Inifity for signaling video is live - if (seconds < 0) { - seconds = Infinity; - } +Component.registerComponent('FullscreenToggle', FullscreenToggle); - if (seconds !== this.cache_.duration) { - // Cache the last set value for optimized scrubbing (esp. Flash) - this.cache_.duration = seconds; +/** + * Check if volume control is supported and if it isn't hide the + * `Component` that was passed using the `vjs-hidden` class. + * + * @param {Component} self + * The component that should be hidden if volume is unsupported + * + * @param {Player} player + * A reference to the player + * + * @private + */ +var checkVolumeSupport = function checkVolumeSupport(self, player) { + // hide volume controls when they're not supported by the current tech + if (player.tech_ && !player.tech_.featuresVolumeControl) { + self.addClass('vjs-hidden'); + } - if (seconds === Infinity) { - this.addClass('vjs-live'); - } else { - this.removeClass('vjs-live'); - } - /** - * @event Player#durationchange - * @type {EventTarget~Event} - */ - this.trigger('durationchange'); + self.on(player, 'loadstart', function () { + if (!player.tech_.featuresVolumeControl) { + self.addClass('vjs-hidden'); + } else { + self.removeClass('vjs-hidden'); } - }; + }); +}; + +/** + * @file volume-level.js + */ +/** + * Shows volume level + * + * @extends Component + */ + +var VolumeLevel = function (_Component) { + inherits(VolumeLevel, _Component); + + function VolumeLevel() { + classCallCheck(this, VolumeLevel); + return possibleConstructorReturn(this, _Component.apply(this, arguments)); + } /** - * Calculates how much time is left in the video. Not part - * of the native video API. + * Create the `Component`'s DOM element * - * @return {number} - * The time remaining in seconds + * @return {Element} + * The element that was created. */ + VolumeLevel.prototype.createEl = function createEl() { + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-volume-level', + innerHTML: '' + }); + }; + return VolumeLevel; +}(Component); - Player.prototype.remainingTime = function remainingTime() { - return this.duration() - this.currentTime(); - }; +Component.registerComponent('VolumeLevel', VolumeLevel); - // - // Kind of like an array of portions of the video that have been downloaded. +/** + * @file volume-bar.js + */ +// Required children +/** + * The bar that contains the volume level and can be clicked on to adjust the level + * + * @extends Slider + */ + +var VolumeBar = function (_Slider) { + inherits(VolumeBar, _Slider); /** - * Get a TimeRange object with an array of the times of the video - * that have been downloaded. If you just want the percent of the - * video that's been downloaded, use bufferedPercent. + * Creates an instance of this class. * - * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered} + * @param {Player} player + * The `Player` that this class should be attached to. * - * @return {TimeRange} - * A mock TimeRange object (following HTML spec) + * @param {Object} [options] + * The key/value store of player options. */ + function VolumeBar(player, options) { + classCallCheck(this, VolumeBar); + var _this = possibleConstructorReturn(this, _Slider.call(this, player, options)); - Player.prototype.buffered = function buffered() { - var buffered = this.techGet_('buffered'); - - if (!buffered || !buffered.length) { - buffered = (0, _timeRanges.createTimeRange)(0, 0); - } - - return buffered; - }; + _this.on('slideractive', _this.updateLastVolume_); + _this.on(player, 'volumechange', _this.updateARIAAttributes); + player.ready(function () { + return _this.updateARIAAttributes(); + }); + return _this; + } /** - * Get the percent (as a decimal) of the video that's been downloaded. - * This method is not a part of the native HTML video API. + * Create the `Component`'s DOM element * - * @return {number} - * A decimal between 0 and 1 representing the percent - * that is bufferred 0 being 0% and 1 being 100% + * @return {Element} + * The element that was created. */ - Player.prototype.bufferedPercent = function bufferedPercent() { - return (0, _buffer.bufferedPercent)(this.buffered(), this.duration()); + VolumeBar.prototype.createEl = function createEl$$1() { + return _Slider.prototype.createEl.call(this, 'div', { + className: 'vjs-volume-bar vjs-slider-bar' + }, { + 'aria-label': this.localize('Volume Level'), + 'aria-live': 'polite' + }); }; /** - * Get the ending time of the last buffered time range - * This is used in the progress bar to encapsulate all time ranges. + * Handle mouse down on volume bar * - * @return {number} - * The end of the last buffered time range + * @param {EventTarget~Event} event + * The `mousedown` event that caused this to run. + * + * @listens mousedown */ - Player.prototype.bufferedEnd = function bufferedEnd() { - var buffered = this.buffered(); - var duration = this.duration(); - var end = buffered.end(buffered.length - 1); - - if (end > duration) { - end = duration; + VolumeBar.prototype.handleMouseDown = function handleMouseDown(event) { + if (!isSingleLeftClick(event)) { + return; } - return end; + _Slider.prototype.handleMouseDown.call(this, event); }; /** - * Get or set the current volume of the media + * Handle movement events on the {@link VolumeMenuButton}. * - * @param {number} [percentAsDecimal] - * The new volume as a decimal percent: - * - 0 is muted/0%/off - * - 1.0 is 100%/full - * - 0.5 is half volume or 50% + * @param {EventTarget~Event} event + * The event that caused this function to run. * - * @return {number} - * The current volume as a percent when getting + * @listens mousemove */ - Player.prototype.volume = function volume(percentAsDecimal) { - var vol = void 0; + VolumeBar.prototype.handleMouseMove = function handleMouseMove(event) { + if (!isSingleLeftClick(event)) { + return; + } - if (percentAsDecimal !== undefined) { - // Force value to between 0 and 1 - vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); - this.cache_.volume = vol; - this.techCall_('setVolume', vol); + this.checkMuted(); + this.player_.volume(this.calculateDistance(event)); + }; - if (vol > 0) { - this.lastVolume_(vol); - } + /** + * If the player is muted unmute it. + */ - return; - } - // Default to 1 when returning current volume. - vol = parseFloat(this.techGet_('volume')); - return isNaN(vol) ? 1 : vol; + VolumeBar.prototype.checkMuted = function checkMuted() { + if (this.player_.muted()) { + this.player_.muted(false); + } }; /** - * Get the current muted state, or turn mute on or off - * - * @param {boolean} [muted] - * - true to mute - * - false to unmute + * Get percent of volume level * - * @return {boolean} - * - true if mute is on and getting - * - false if mute is off and getting + * @return {number} + * Volume level percent as a decimal number. */ - Player.prototype.muted = function muted(_muted) { - if (_muted !== undefined) { - this.techCall_('setMuted', _muted); - return; + VolumeBar.prototype.getPercent = function getPercent() { + if (this.player_.muted()) { + return 0; } - return this.techGet_('muted') || false; + return this.player_.volume(); }; /** - * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted - * indicates the state of muted on intial playback. - * - * ```js - * var myPlayer = videojs('some-player-id'); - * - * myPlayer.src("http://www.example.com/path/to/video.mp4"); - * - * // get, should be false - * console.log(myPlayer.defaultMuted()); - * // set to true - * myPlayer.defaultMuted(true); - * // get should be true - * console.log(myPlayer.defaultMuted()); - * ``` - * - * @param {boolean} [defaultMuted] - * - true to mute - * - false to unmute - * - * @return {boolean|Player} - * - true if defaultMuted is on and getting - * - false if defaultMuted is off and getting - * - A reference to the current player when setting + * Increase volume level for keyboard users */ - Player.prototype.defaultMuted = function defaultMuted(_defaultMuted) { - if (_defaultMuted !== undefined) { - return this.techCall_('setDefaultMuted', _defaultMuted); - } - return this.techGet_('defaultMuted') || false; + VolumeBar.prototype.stepForward = function stepForward() { + this.checkMuted(); + this.player_.volume(this.player_.volume() + 0.1); }; /** - * Get the last volume, or set it - * - * @param {number} [percentAsDecimal] - * The new last volume as a decimal percent: - * - 0 is muted/0%/off - * - 1.0 is 100%/full - * - 0.5 is half volume or 50% - * - * @return {number} - * the current value of lastVolume as a percent when getting - * - * @private + * Decrease volume level for keyboard users */ - Player.prototype.lastVolume_ = function lastVolume_(percentAsDecimal) { - if (percentAsDecimal !== undefined && percentAsDecimal !== 0) { - this.cache_.lastVolume = percentAsDecimal; - return; - } - return this.cache_.lastVolume; + VolumeBar.prototype.stepBack = function stepBack() { + this.checkMuted(); + this.player_.volume(this.player_.volume() - 0.1); }; /** - * Check if current tech can support native fullscreen - * (e.g. with built in controls like iOS, so not our flash swf) + * Update ARIA accessibility attributes * - * @return {boolean} - * if native fullscreen is supported + * @param {EventTarget~Event} [event] + * The `volumechange` event that caused this function to run. + * + * @listens Player#volumechange */ - Player.prototype.supportsFullScreen = function supportsFullScreen() { - return this.techGet_('supportsFullScreen') || false; + VolumeBar.prototype.updateARIAAttributes = function updateARIAAttributes(event) { + var ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_(); + + this.el_.setAttribute('aria-valuenow', ariaValue); + this.el_.setAttribute('aria-valuetext', ariaValue + '%'); }; /** - * Check if the player is in fullscreen mode or tell the player that it - * is or is not in fullscreen mode. - * - * > 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] - * Set the players current fullscreen state + * Returns the current value of the player volume as a percentage * - * @return {boolean} - * - true if fullscreen is on and getting - * - false if fullscreen is off and getting + * @private */ - Player.prototype.isFullscreen = function isFullscreen(isFS) { - if (isFS !== undefined) { - this.isFullscreen_ = !!isFS; - return; - } - return !!this.isFullscreen_; + VolumeBar.prototype.volumeAsPercentage_ = function volumeAsPercentage_() { + return Math.round(this.player_.volume() * 100); }; /** - * Increase the size of the video to full screen - * 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. + * When user starts dragging the VolumeBar, store the volume and listen for + * the end of the drag. When the drag ends, if the volume was set to zero, + * set lastVolume to the stored volume. * - * @fires Player#fullscreenchange + * @listens slideractive + * @private */ - Player.prototype.requestFullscreen = function requestFullscreen() { - var fsApi = _fullscreenApi2['default']; + VolumeBar.prototype.updateLastVolume_ = function updateLastVolume_() { + var _this2 = this; - this.isFullscreen(true); + var volumeBeforeDrag = this.player_.volume(); - if (fsApi.requestFullscreen) { - // the browser supports going fullscreen at the element level so we can - // take the controls fullscreen as well as the video + this.one('sliderinactive', function () { + if (_this2.player_.volume() === 0) { + _this2.player_.lastVolume_(volumeBeforeDrag); + } + }); + }; - // 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(_document2['default'], fsApi.fullscreenchange, Fn.bind(this, function documentFullscreenChange(e) { - this.isFullscreen(_document2['default'][fsApi.fullscreenElement]); + return VolumeBar; +}(Slider); - // If cancelling fullscreen, remove event listener. - if (this.isFullscreen() === false) { - Events.off(_document2['default'], fsApi.fullscreenchange, documentFullscreenChange); - } - /** - * @event Player#fullscreenchange - * @type {EventTarget~Event} - */ - this.trigger('fullscreenchange'); - })); +/** + * Default options for the `VolumeBar` + * + * @type {Object} + * @private + */ - 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(); - /** - * @event Player#fullscreenchange - * @type {EventTarget~Event} - */ - this.trigger('fullscreenchange'); - } - }; - /** - * Return the video to its normal size after having been in full screen mode - * - * @fires Player#fullscreenchange - */ +VolumeBar.prototype.options_ = { + children: ['volumeLevel'], + barName: 'volumeLevel' +}; +/** + * Call the update event for this Slider when this event happens on the player. + * + * @type {string} + */ +VolumeBar.prototype.playerEvent = 'volumechange'; - Player.prototype.exitFullscreen = function exitFullscreen() { - var fsApi = _fullscreenApi2['default']; +Component.registerComponent('VolumeBar', VolumeBar); - this.isFullscreen(false); +/** + * @file volume-control.js + */ +// Required children +/** + * The component for controlling the volume level + * + * @extends Component + */ - // Check for browser element fullscreen support - if (fsApi.requestFullscreen) { - _document2['default'][fsApi.exitFullscreen](); - } else if (this.tech_.supportsFullScreen()) { - this.techCall_('exitFullScreen'); - } else { - this.exitFullWindow(); - /** - * @event Player#fullscreenchange - * @type {EventTarget~Event} - */ - this.trigger('fullscreenchange'); - } - }; +var VolumeControl = function (_Component) { + inherits(VolumeControl, _Component); /** - * When fullscreen isn't supported we can stretch the - * video container to as wide as the browser will let us. + * Creates an instance of this class. * - * @fires Player#enterFullWindow + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options={}] + * The key/value store of player options. */ + function VolumeControl(player) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + classCallCheck(this, VolumeControl); + options.vertical = options.vertical || false; - Player.prototype.enterFullWindow = function enterFullWindow() { - this.isFullWindow = true; + // Pass the vertical option down to the VolumeBar if + // the VolumeBar is turned on. + if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) { + options.volumeBar = options.volumeBar || {}; + options.volumeBar.vertical = options.vertical; + } - // Storing original doc overflow value to return to when fullscreen is off - this.docOrigOverflow = _document2['default'].documentElement.style.overflow; + // hide this control if volume support is missing + var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); - // Add listener for esc key to exit fullscreen - Events.on(_document2['default'], 'keydown', Fn.bind(this, this.fullWindowOnEscKey)); + checkVolumeSupport(_this, player); - // Hide any scroll bars - _document2['default'].documentElement.style.overflow = 'hidden'; + _this.throttledHandleMouseMove = throttle(bind(_this, _this.handleMouseMove), 25); - // Apply fullscreen styles - Dom.addClass(_document2['default'].body, 'vjs-full-window'); + _this.on('mousedown', _this.handleMouseDown); + _this.on('touchstart', _this.handleMouseDown); - /** - * @event Player#enterFullWindow - * @type {EventTarget~Event} - */ - this.trigger('enterFullWindow'); - }; + // while the slider is active (the mouse has been pressed down and + // is dragging) or in focus we do not want to hide the VolumeBar + _this.on(_this.volumeBar, ['focus', 'slideractive'], function () { + _this.volumeBar.addClass('vjs-slider-active'); + _this.addClass('vjs-slider-active'); + _this.trigger('slideractive'); + }); + + _this.on(_this.volumeBar, ['blur', 'sliderinactive'], function () { + _this.volumeBar.removeClass('vjs-slider-active'); + _this.removeClass('vjs-slider-active'); + _this.trigger('sliderinactive'); + }); + return _this; + } /** - * Check for call to either exit full window or - * full screen on ESC key + * Create the `Component`'s DOM element * - * @param {string} event - * Event to check for key press + * @return {Element} + * The element that was created. */ - Player.prototype.fullWindowOnEscKey = function fullWindowOnEscKey(event) { - if (event.keyCode === 27) { - if (this.isFullscreen() === true) { - this.exitFullscreen(); - } else { - this.exitFullWindow(); - } - } - }; - + VolumeControl.prototype.createEl = function createEl() { + var orientationClass = 'vjs-volume-horizontal'; + + if (this.options_.vertical) { + orientationClass = 'vjs-volume-vertical'; + } + + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-volume-control vjs-control ' + orientationClass + }); + }; + /** - * Exit full window + * Handle `mousedown` or `touchstart` events on the `VolumeControl`. * - * @fires Player#exitFullWindow + * @param {EventTarget~Event} event + * `mousedown` or `touchstart` event that triggered this function + * + * @listens mousedown + * @listens touchstart */ - Player.prototype.exitFullWindow = function exitFullWindow() { - this.isFullWindow = false; - Events.off(_document2['default'], 'keydown', this.fullWindowOnEscKey); + VolumeControl.prototype.handleMouseDown = function handleMouseDown(event) { + var doc = this.el_.ownerDocument; - // Unhide scroll bars. - _document2['default'].documentElement.style.overflow = this.docOrigOverflow; + this.on(doc, 'mousemove', this.throttledHandleMouseMove); + this.on(doc, 'touchmove', this.throttledHandleMouseMove); + this.on(doc, 'mouseup', this.handleMouseUp); + this.on(doc, 'touchend', this.handleMouseUp); + }; - // Remove fullscreen styles - Dom.removeClass(_document2['default'].body, 'vjs-full-window'); + /** + * Handle `mouseup` or `touchend` events on the `VolumeControl`. + * + * @param {EventTarget~Event} event + * `mouseup` or `touchend` event that triggered this function. + * + * @listens touchend + * @listens mouseup + */ - // Resize the box, controller, and poster to original sizes - // this.positionAll(); - /** - * @event Player#exitFullWindow - * @type {EventTarget~Event} - */ - this.trigger('exitFullWindow'); + + VolumeControl.prototype.handleMouseUp = function handleMouseUp(event) { + var doc = this.el_.ownerDocument; + + this.off(doc, 'mousemove', this.throttledHandleMouseMove); + this.off(doc, 'touchmove', this.throttledHandleMouseMove); + this.off(doc, 'mouseup', this.handleMouseUp); + this.off(doc, 'touchend', this.handleMouseUp); }; /** - * Check whether the player can play a given mimetype - * - * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype + * Handle `mousedown` or `touchstart` events on the `VolumeControl`. * - * @param {string} type - * The mimetype to check + * @param {EventTarget~Event} event + * `mousedown` or `touchstart` event that triggered this function * - * @return {string} - * 'probably', 'maybe', or '' (empty string) + * @listens mousedown + * @listens touchstart */ - Player.prototype.canPlayType = function canPlayType(type) { - var can = void 0; + VolumeControl.prototype.handleMouseMove = function handleMouseMove(event) { + this.volumeBar.handleMouseMove(event); + }; - // Loop through each playback technology in the options order - for (var i = 0, j = this.options_.techOrder; i < j.length; i++) { - var techName = j[i]; - var tech = _tech2['default'].getTech(techName); + return VolumeControl; +}(Component); - // Support old behavior of techs being registered as components. - // Remove once that deprecated behavior is removed. - if (!tech) { - tech = _component2['default'].getComponent(techName); - } +/** + * Default options for the `VolumeControl` + * + * @type {Object} + * @private + */ - // Check if the current tech is defined before continuing - if (!tech) { - _log2['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); +VolumeControl.prototype.options_ = { + children: ['volumeBar'] +}; - if (can) { - return can; - } - } - } +Component.registerComponent('VolumeControl', VolumeControl); - return ''; - }; +/** + * @file mute-toggle.js + */ +/** + * A button component for muting the audio. + * + * @extends Button + */ + +var MuteToggle = function (_Button) { + inherits(MuteToggle, _Button); /** - * Select source based on tech-order or source-order - * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise, - * defaults to tech-order selection + * Creates an instance of this class. * - * @param {Array} sources - * The sources for a media asset + * @param {Player} player + * The `Player` that this class should be attached to. * - * @return {Object|boolean} - * Object of source and tech order or false + * @param {Object} [options] + * The key/value store of player options. */ + function MuteToggle(player, options) { + classCallCheck(this, MuteToggle); + // hide this control if volume support is missing + var _this = possibleConstructorReturn(this, _Button.call(this, player, options)); - Player.prototype.selectSource = function selectSource(sources) { - var _this5 = this; - - // Get only the techs specified in `techOrder` that exist and are supported by the - // current platform - var techs = this.options_.techOrder.map(function (techName) { - return [techName, _tech2['default'].getTech(techName)]; - }).filter(function (_ref) { - var techName = _ref[0], - tech = _ref[1]; + checkVolumeSupport(_this, player); - // Check if the current tech is defined before continuing - if (tech) { - // Check if the browser supports this technology - return tech.isSupported(); - } + _this.on(player, ['loadstart', 'volumechange'], _this.update); + return _this; + } - _log2['default'].error('The "' + techName + '" tech is undefined. Skipped browser support check for that tech.'); - return false; - }); + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ - // 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 = void 0; - outerArray.some(function (outerChoice) { - return innerArray.some(function (innerChoice) { - found = tester(outerChoice, innerChoice); + MuteToggle.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-mute-control ' + _Button.prototype.buildCSSClass.call(this); + }; - if (found) { - return true; - } - }); - }); + /** + * This gets called when an `MuteToggle` is "clicked". See + * {@link ClickableComponent} for more detailed information on what a click can be. + * + * @param {EventTarget~Event} [event] + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click + */ - return found; - }; - var foundSourceAndTech = void 0; - var flip = function flip(fn) { - return function (a, b) { - return fn(b, a); - }; - }; - var finder = function finder(_ref2, source) { - var techName = _ref2[0], - tech = _ref2[1]; + MuteToggle.prototype.handleClick = function handleClick(event) { + var vol = this.player_.volume(); + var lastVolume = this.player_.lastVolume_(); - if (tech.canPlaySource(source, _this5.options_[techName.toLowerCase()])) { - return { source: source, tech: techName }; - } - }; + if (vol === 0) { + var volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume; - // 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)); + this.player_.volume(volumeToSet); + this.player_.muted(false); } else { - // Tech-first ordering - foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder); + this.player_.muted(this.player_.muted() ? false : true); } - - 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. + * Update the `MuteToggle` button based on the state of `volume` and `muted` + * on the player. * - * @param {Tech~SourceObject|Tech~SourceObject[]} [source] - * One SourceObject or an array of SourceObjects + * @param {EventTarget~Event} [event] + * The {@link Player#loadstart} event if this function was called + * through an event. * - * @return {string} - * The current video source when getting + * @listens Player#loadstart + * @listens Player#volumechange */ - Player.prototype.src = function src(source) { - var _this6 = this; + MuteToggle.prototype.update = function update(event) { + this.updateIcon_(); + this.updateControlText_(); + }; - // getter usage - if (typeof source === 'undefined') { - return this.cache_.src; - } - // filter out invalid sources and turn our source into - // an array of source objects - var sources = (0, _filterSource2['default'])(source); + /** + * Update the appearance of the `MuteToggle` icon. + * + * Possible states (given `level` variable below): + * - 0: crossed out + * - 1: zero bars of volume + * - 2: one bar of volume + * - 3: two bars of volume + * + * @private + */ - // if a source was passed in then it is invalid because - // it was filtered to a zero length Array. So we have to - // show an error - if (!sources.length) { - this.setTimeout(function () { - this.error({ code: 4, message: this.localize(this.options_.notSupportedMessage) }); - }, 0); - return; - } - // intial sources - this.cache_.sources = sources; - this.changingSrc_ = true; + MuteToggle.prototype.updateIcon_ = function updateIcon_() { + var vol = this.player_.volume(); + var level = 3; - // intial source - this.cache_.source = sources[0]; - - // middlewareSource is the source after it has been changed by middleware - middleware.setSource(this, sources[0], function (middlewareSource, mws) { - _this6.middleware_ = mws; - - var err = _this6.src_(middlewareSource); - - if (err) { - if (sources.length > 1) { - return _this6.src(sources.slice(1)); - } - - // We need to wrap this in a timeout to give folks a chance to add error event handlers - _this6.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 - _this6.triggerReady(); - - return; - } - - _this6.changingSrc_ = false; - // video element listed source - _this6.cache_.src = middlewareSource.src; + if (vol === 0 || this.player_.muted()) { + level = 0; + } else if (vol < 0.33) { + level = 1; + } else if (vol < 0.67) { + level = 2; + } - middleware.setTech(mws, _this6.tech_); - }); + // TODO improve muted icon classes + for (var i = 0; i < 4; i++) { + removeClass(this.el_, 'vjs-vol-' + i); + } + addClass(this.el_, 'vjs-vol-' + level); }; /** - * Set the source object on the tech, returns a boolean that indicates wether - * there is a tech that can play the source or not - * - * @param {Tech~SourceObject} source - * The source object to set on the Tech - * - * @return {Boolean} - * - True if there is no Tech to playback this source - * - False otherwise + * If `muted` has changed on the player, update the control text + * (`title` attribute on `vjs-mute-control` element and content of + * `vjs-control-text` element). * * @private */ - Player.prototype.src_ = function src_(source) { - var sourceTech = this.selectSource([source]); + MuteToggle.prototype.updateControlText_ = function updateControlText_() { + var soundOff = this.player_.muted() || this.player_.volume() === 0; + var text = soundOff ? 'Unmute' : 'Mute'; - if (!sourceTech) { - return true; + if (this.controlText() !== text) { + this.controlText(text); } + }; - if (!(0, _toTitleCase.titleCaseEquals)(sourceTech.tech, this.techName_)) { - this.changingSrc_ = true; - - // load this technology with the chosen source - this.loadTech_(sourceTech.tech, sourceTech.source); - return false; - } + return MuteToggle; +}(Button); - // wait until the tech is ready to set the source - this.ready(function () { +/** + * The text that should display over the `MuteToggle`s controls. Added for localization. + * + * @type {string} + * @private + */ - // 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 (this.tech_.constructor.prototype.hasOwnProperty('setSource')) { - this.techCall_('setSource', source); - } else { - this.techCall_('src', source.src); - } - if (this.options_.preload === 'auto') { - this.load(); - } +MuteToggle.prototype.controlText_ = 'Mute'; - if (this.options_.autoplay) { - this.play(); - } +Component.registerComponent('MuteToggle', MuteToggle); - // Set the source synchronously if possible (#2326) - }, true); +/** + * @file volume-control.js + */ +// Required children +/** + * A Component to contain the MuteToggle and VolumeControl so that + * they can work together. + * + * @extends Component + */ - return false; - }; +var VolumePanel = function (_Component) { + inherits(VolumePanel, _Component); /** - * Begin loading the src data. + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options={}] + * The key/value store of player options. */ + function VolumePanel(player) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + classCallCheck(this, VolumePanel); + if (typeof options.inline !== 'undefined') { + options.inline = options.inline; + } else { + options.inline = true; + } - Player.prototype.load = function load() { - this.techCall_('load'); - }; + // pass the inline option down to the VolumeControl as vertical if + // the VolumeControl is on. + if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) { + options.volumeControl = options.volumeControl || {}; + options.volumeControl.vertical = !options.inline; + } - /** - * Reset the player. Loads the first tech in the techOrder, - * and calls `reset` on the tech`. - */ + // hide this control if volume support is missing + var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); + checkVolumeSupport(_this, player); - Player.prototype.reset = function reset() { - this.loadTech_(this.options_.techOrder[0], null); - this.techCall_('reset'); - }; + // while the slider is active (the mouse has been pressed down and + // is dragging) we do not want to hide the VolumeBar + _this.on(_this.volumeControl, ['slideractive'], _this.sliderActive_); + + _this.on(_this.volumeControl, ['sliderinactive'], _this.sliderInactive_); + return _this; + } /** - * Returns all of the current source objects. + * Add vjs-slider-active class to the VolumePanel * - * @return {Tech~SourceObject[]} - * The current source objects + * @listens VolumeControl#slideractive + * @private */ - Player.prototype.currentSources = function currentSources() { - var source = this.currentSource(); - var sources = []; - - // assume `{}` or `{ src }` - if (Object.keys(source).length !== 0) { - sources.push(source); - } - - return this.cache_.sources || sources; + VolumePanel.prototype.sliderActive_ = function sliderActive_() { + this.addClass('vjs-slider-active'); }; /** - * Returns the current source object. + * Removes vjs-slider-active class to the VolumePanel * - * @return {Tech~SourceObject} - * The current source object + * @listens VolumeControl#sliderinactive + * @private */ - Player.prototype.currentSource = function currentSource() { - return this.cache_.source || {}; + VolumePanel.prototype.sliderInactive_ = function sliderInactive_() { + this.removeClass('vjs-slider-active'); }; /** - * 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. + * Create the `Component`'s DOM element * - * @return {string} - * The current source + * @return {Element} + * The element that was created. */ - Player.prototype.currentSrc = function currentSrc() { - return this.currentSource() && this.currentSource().src || ''; + VolumePanel.prototype.createEl = function createEl() { + var orientationClass = 'vjs-volume-panel-horizontal'; + + if (!this.options_.inline) { + orientationClass = 'vjs-volume-panel-vertical'; + } + + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-volume-panel vjs-control ' + orientationClass + }); }; - /** - * 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 - */ + return VolumePanel; +}(Component); + +/** + * Default options for the `VolumeControl` + * + * @type {Object} + * @private + */ - Player.prototype.currentType = function currentType() { - return this.currentSource() && this.currentSource().type || ''; - }; +VolumePanel.prototype.options_ = { + children: ['muteToggle', 'volumeControl'] +}; + +Component.registerComponent('VolumePanel', VolumePanel); + +/** + * @file menu.js + */ +/** + * The Menu component is used to build popup menus, including subtitle and + * captions selection menus. + * + * @extends Component + */ + +var Menu = function (_Component) { + inherits(Menu, _Component); /** - * Get or set the preload attribute + * Create an instance of this class. * - * @param {boolean} [value] - * - true means that we should preload - * - false maens that we should not preload + * @param {Player} player + * the player that this component should attach to + * + * @param {Object} [options] + * Object of option names and values * - * @return {string} - * The preload attribute value when getting */ + function Menu(player, options) { + classCallCheck(this, Menu); + var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); - Player.prototype.preload = function preload(value) { - if (value !== undefined) { - this.techCall_('setPreload', value); - this.options_.preload = value; - return; + if (options) { + _this.menuButton_ = options.menuButton; } - return this.techGet_('preload'); - }; + + _this.focusedChild_ = -1; + + _this.on('keydown', _this.handleKeyPress); + return _this; + } /** - * Get or set the autoplay attribute. + * Add a {@link MenuItem} to the menu. * - * @param {boolean} [value] - * - true means that we should autoplay - * - false means that we should not autoplay + * @param {Object|string} component + * The name or instance of the `MenuItem` to add. * - * @return {string} - * The current value of autoplay when getting */ - Player.prototype.autoplay = function autoplay(value) { - if (value !== undefined) { - this.techCall_('setAutoplay', value); - this.options_.autoplay = value; - return; - } - return this.techGet_('autoplay', value); + Menu.prototype.addItem = function addItem(component) { + this.addChild(component); + component.on('click', bind(this, function (event) { + // Unpress the associated MenuButton, and move focus back to it + if (this.menuButton_) { + this.menuButton_.unpressButton(); + + // don't focus menu button if item is a caption settings item + // because focus will move elsewhere and it logs an error on IE8 + if (component.name() !== 'CaptionSettingsMenuItem') { + this.menuButton_.focus(); + } + } + })); }; /** - * Set or unset the playsinline attribute. - * Playsinline tells the browser that non-fullscreen playback is preferred. - * - * @param {boolean} [value] - * - true means that we should try to play inline by default - * - false means that we should use the browser's default playback mode, - * which in most cases is inline. iOS Safari is a notable exception - * and plays fullscreen by default. - * - * @return {string|Player} - * - the current value of playsinline - * - the player when setting + * Create the `Menu`s DOM element. * - * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} + * @return {Element} + * the element that was created */ - Player.prototype.playsinline = function playsinline(value) { - if (value !== undefined) { - this.techCall_('setPlaysinline', value); - this.options_.playsinline = value; - return this; - } - return this.techGet_('playsinline'); + Menu.prototype.createEl = function createEl$$1() { + var contentElType = this.options_.contentElType || 'ul'; + + this.contentEl_ = 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.appendChild(this.contentEl_); + + // Prevent clicks from bubbling up. Needed for Menu Buttons, + // where a click on the parent is significant + on(el, 'click', function (event) { + event.preventDefault(); + event.stopImmediatePropagation(); + }); + + return el; + }; + + Menu.prototype.dispose = function dispose() { + this.contentEl_ = null; + + _Component.prototype.dispose.call(this); }; /** - * Get or set the loop attribute on the video element. + * Handle a `keydown` event on this menu. This listener is added in the constructor. * - * @param {boolean} [value] - * - true means that we should loop the video - * - false means that we should not loop the video + * @param {EventTarget~Event} event + * A `keydown` event that happened on the menu. * - * @return {string} - * The current value of loop when getting + * @listens keydown */ - Player.prototype.loop = function loop(value) { - if (value !== undefined) { - this.techCall_('setLoop', value); - this.options_.loop = value; - return; + Menu.prototype.handleKeyPress = function handleKeyPress(event) { + // Left and Down Arrows + if (event.which === 37 || event.which === 40) { + event.preventDefault(); + this.stepForward(); + + // Up and Right Arrows + } else if (event.which === 38 || event.which === 39) { + event.preventDefault(); + this.stepBack(); } - return this.techGet_('loop'); }; /** - * Get or set the poster image source url - * - * @fires Player#posterchange - * - * @param {string} [src] - * Poster image source URL - * - * @return {string} - * The current value of poster when getting + * Move to next (lower) menu item for keyboard users. */ - Player.prototype.poster = function poster(src) { - if (src === undefined) { - return this.poster_; - } + Menu.prototype.stepForward = function stepForward() { + var stepChild = 0; - // The correct way to remove a poster is to set as an empty string - // other falsey values will throw errors - if (!src) { - src = ''; + if (this.focusedChild_ !== undefined) { + stepChild = this.focusedChild_ + 1; } - - // 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 event fires when the poster image is changed on the player. - * - * @event Player#posterchange - * @type {EventTarget~Event} - */ - this.trigger('posterchange'); + this.focus(stepChild); }; /** - * 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 - * source if the player user hasn't set a poster through - * the normal APIs. - * - * @fires Player#posterchange - * @listens Tech#posterchange - * @private + * Move to previous (higher) menu item for keyboard users. */ - Player.prototype.handleTechPosterChange_ = function handleTechPosterChange_() { - if (!this.poster_ && this.tech_ && this.tech_.poster) { - this.poster_ = this.tech_.poster() || ''; + Menu.prototype.stepBack = function stepBack() { + var stepChild = 0; - // Let components know the poster has changed - this.trigger('posterchange'); + if (this.focusedChild_ !== undefined) { + stepChild = this.focusedChild_ - 1; } + this.focus(stepChild); }; /** - * Get or set whether or not the controls are showing. - * - * @fires Player#controlsenabled - * - * @param {boolean} [bool] - * - true to turn controls on - * - false to turn controls off + * Set focus on a {@link MenuItem} in the `Menu`. * - * @return {boolean} - * The current value of controls when getting + * @param {Object|string} [item=0] + * Index of child item set focus on. */ - Player.prototype.controls = function controls(bool) { - if (bool !== undefined) { - bool = !!bool; + Menu.prototype.focus = function focus() { + var item = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; - // Don't trigger a change event unless it actually changed - if (this.controls_ !== bool) { - this.controls_ = bool; + var children = this.children().slice(); + var haveTitle = children.length && children[0].className && /vjs-menu-title/.test(children[0].className); - if (this.usingNativeControls()) { - this.techCall_('setControls', bool); - } + if (haveTitle) { + children.shift(); + } - if (bool) { - this.removeClass('vjs-controls-disabled'); - this.addClass('vjs-controls-enabled'); - /** - * @event Player#controlsenabled - * @type {EventTarget~Event} - */ - this.trigger('controlsenabled'); + if (children.length > 0) { + if (item < 0) { + item = 0; + } else if (item >= children.length) { + item = children.length - 1; + } - if (!this.usingNativeControls()) { - this.addTechControlsListeners_(); - } - } else { - this.removeClass('vjs-controls-enabled'); - this.addClass('vjs-controls-disabled'); - /** - * @event Player#controlsdisabled - * @type {EventTarget~Event} - */ - this.trigger('controlsdisabled'); + this.focusedChild_ = item; - if (!this.usingNativeControls()) { - this.removeTechControlsListeners_(); - } - } - } - return; + children[item].el_.focus(); } - 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** - * - * @fires Player#usingnativecontrols - * @fires Player#usingcustomcontrols - * - * @param {boolean} [bool] - * - true to turn native controls on - * - false to turn native controls off - * - * @return {boolean} - * The current value of native controls when getting - */ - - - Player.prototype.usingNativeControls = function usingNativeControls(bool) { - if (bool !== undefined) { - bool = !!bool; + return Menu; +}(Component); - // 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'); +Component.registerComponent('Menu', Menu); - /** - * player is using the native device controls - * - * @event Player#usingnativecontrols - * @type {EventTarget~Event} - */ - this.trigger('usingnativecontrols'); - } else { - this.removeClass('vjs-using-native-controls'); +/** + * @file menu-button.js + */ +/** + * A `MenuButton` class for any popup {@link Menu}. + * + * @extends Component + */ - /** - * player is using the custom HTML controls - * - * @event Player#usingcustomcontrols - * @type {EventTarget~Event} - */ - this.trigger('usingcustomcontrols'); - } - } - return; - } - return !!this.usingNativeControls_; - }; +var MenuButton = function (_Component) { + inherits(MenuButton, _Component); /** - * Set or get the current MediaError - * - * @fires Player#error + * Creates an instance of this class. * - * @param {MediaError|string|number} [err] - * A MediaError or a string/number to be turned - * into a MediaError + * @param {Player} player + * The `Player` that this class should be attached to. * - * @return {MediaError|null} - * The current MediaError when getting (or null) + * @param {Object} [options={}] + * The key/value store of player options. */ + function MenuButton(player) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + classCallCheck(this, MenuButton); + var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); - Player.prototype.error = function error(err) { - if (err === undefined) { - return this.error_ || null; - } + _this.menuButton_ = new Button(player, options); - // restoring to default - if (err === null) { - this.error_ = err; - this.removeClass('vjs-error'); - if (this.errorDisplay) { - this.errorDisplay.close(); - } - return; - } + _this.menuButton_.controlText(_this.controlText_); + _this.menuButton_.el_.setAttribute('aria-haspopup', 'true'); - this.error_ = new _mediaError2['default'](err); + // Add buildCSSClass values to the button, not the wrapper + var buttonClass = Button.prototype.buildCSSClass(); - // add the vjs-error classname to the player - this.addClass('vjs-error'); + _this.menuButton_.el_.className = _this.buildCSSClass() + ' ' + buttonClass; + _this.menuButton_.removeClass('vjs-control'); - // log the name of the error type and any message - // ie8 just logs "[object object]" if you just log the error object - _log2['default'].error('(CODE:' + this.error_.code + ' ' + _mediaError2['default'].errorTypes[this.error_.code] + ')', this.error_.message, this.error_); + _this.addChild(_this.menuButton_); - /** - * @event Player#error - * @type {EventTarget~Event} - */ - this.trigger('error'); + _this.update(); - return; - }; + _this.enabled_ = true; + + _this.on(_this.menuButton_, 'tap', _this.handleClick); + _this.on(_this.menuButton_, 'click', _this.handleClick); + _this.on(_this.menuButton_, 'focus', _this.handleFocus); + _this.on(_this.menuButton_, 'blur', _this.handleBlur); + + _this.on('keydown', _this.handleSubmenuKeyPress); + return _this; + } /** - * Report user activity - * - * @param {Object} event - * Event object + * Update the menu based on the current state of its items. */ - Player.prototype.reportUserActivity = function reportUserActivity(event) { - this.userActivity_ = true; - }; + MenuButton.prototype.update = function update() { + var menu = this.createMenu(); - /** - * Get/set if user is active - * - * @fires Player#useractive - * @fires Player#userinactive - * - * @param {boolean} [bool] - * - true if the user is active - * - false if the user is inactive - * - * @return {boolean} - * The current value of userActive when getting - */ + if (this.menu) { + this.menu.dispose(); + this.removeChild(this.menu); + } + this.menu = menu; + this.addChild(menu); - 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'); - /** - * @event Player#useractive - * @type {EventTarget~Event} - */ - 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(); - }); - } + /** + * Track the state of the menu button + * + * @type {Boolean} + * @private + */ + this.buttonPressed_ = false; + this.menuButton_.el_.setAttribute('aria-expanded', 'false'); - this.removeClass('vjs-user-active'); - this.addClass('vjs-user-inactive'); - /** - * @event Player#userinactive - * @type {EventTarget~Event} - */ - this.trigger('userinactive'); - } - } - return; + if (this.items && this.items.length <= this.hideThreshold_) { + this.hide(); + } else { + this.show(); } - return this.userActive_; }; /** - * Listen for user activity based on timeout value + * Create the menu and add all items to it. * - * @private + * @return {Menu} + * The constructed menu */ - Player.prototype.listenForUserActivity_ = function listenForUserActivity_() { - var mouseInProgress = void 0; - var lastMoveX = void 0; - var lastMoveY = void 0; - 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(); - } - }; + MenuButton.prototype.createMenu = function createMenu() { + var menu = new Menu(this.player_, { menuButton: this }); - 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); - }; + /** + * Hide the menu if the number of items is less than or equal to this threshold. This defaults + * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list + * it here because every time we run `createMenu` we need to reset the value. + * + * @protected + * @type {Number} + */ + this.hideThreshold_ = 0; - var handleMouseUp = function handleMouseUp(event) { - handleActivity(); - // Stop the interval that maintains activity if the mouse/touch is down - this.clearInterval(mouseInProgress); - }; + // Add a title list item to the top + if (this.options_.title) { + var title = createEl('li', { + className: 'vjs-menu-title', + innerHTML: toTitleCase(this.options_.title), + tabIndex: -1 + }); - // Any mouse movement will be considered user activity - this.on('mousedown', handleMouseDown); - this.on('mousemove', handleMouseMove); - this.on('mouseup', handleMouseUp); + this.hideThreshold_ += 1; - // Listen for keyboard navigation - // Shouldn't need to use inProgress interval because of key repeat - this.on('keydown', handleActivity); - this.on('keyup', handleActivity); + menu.children_.unshift(title); + prependTo(title, menu.contentEl()); + } - // 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 = void 0; + this.items = this.createItems(); - 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 activity check loop - // causing a flicker - if (!this.userActivity_) { - this.userActive(false); - } - }, timeout); - } + if (this.items) { + // Add menu items to the menu + for (var i = 0; i < this.items.length; i++) { + menu.addItem(this.items[i]); } - }, 250); + } + + return menu; }; /** - * 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. + * Create the list of menu items. Specific to each subclass. * - * @return {number} - * The current playback rate when getting or 1.0 + * @abstract */ - Player.prototype.playbackRate = function playbackRate(rate) { - if (rate !== undefined) { - this.techCall_('setPlaybackRate', rate); - return; - } - - if (this.tech_ && this.tech_.featuresPlaybackRate) { - return this.techGet_('playbackRate'); - } - return 1.0; - }; + MenuButton.prototype.createItems = function createItems() {}; /** - * Gets or sets the current default playback rate. A default playback rate of - * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance. - * defaultPlaybackRate will only represent what the intial playbackRate of a video was, not - * not the current playbackRate. - * - * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate - * - * @param {number} [rate] - * New default playback rate to set. + * Create the `MenuButtons`s DOM element. * - * @return {number|Player} - * - The default playback rate when getting or 1.0 - * - the player when setting + * @return {Element} + * The element that gets created. */ - Player.prototype.defaultPlaybackRate = function defaultPlaybackRate(rate) { - if (rate !== undefined) { - return this.techCall_('setDefaultPlaybackRate', rate); - } - - if (this.tech_ && this.tech_.featuresPlaybackRate) { - return this.techGet_('defaultPlaybackRate'); - } - return 1.0; + MenuButton.prototype.createEl = function createEl$$1() { + return _Component.prototype.createEl.call(this, 'div', { + className: this.buildWrapperCSSClass() + }, {}); }; /** - * Gets or sets the audio flag - * - * @param {boolean} bool - * - true signals that this is an audio player - * - false signals that this is not an audio player + * Allow sub components to stack CSS class names for the wrapper element * - * @return {boolean} - * The current value of isAudio when getting + * @return {string} + * The constructed wrapper DOM `className` */ - Player.prototype.isAudio = function isAudio(bool) { - if (bool !== undefined) { - this.isAudio_ = !!bool; - return; + MenuButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { + 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 !!this.isAudio_; + // TODO: Fix the CSS so that this isn't necessary + var buttonClass = Button.prototype.buildCSSClass(); + + return 'vjs-menu-button ' + menuButtonClass + ' ' + buttonClass + ' ' + _Component.prototype.buildCSSClass.call(this); }; /** - * A helper method for adding a {@link TextTrack} to our - * {@link TextTrackList}. - * - * In addition to the W3C settings we allow adding additional info through options. - * - * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack - * - * @param {string} [kind] - * the kind of TextTrack you are adding + * Builds the default DOM `className`. * - * @param {string} [label] - * the label to give the TextTrack label - * - * @param {string} [language] - * the language to set on the TextTrack - * - * @return {TextTrack|undefined} - * the TextTrack that was added or undefined - * if there is no tech + * @return {string} + * The DOM `className` for this object. */ - Player.prototype.addTextTrack = function addTextTrack(kind, label, language) { - if (this.tech_) { - return this.tech_.addTextTrack(kind, label, language); + MenuButton.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 + ' ' + _Component.prototype.buildCSSClass.call(this); }; /** - * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}. It will - * automatically removed from the video element whenever the source changes, unless - * manualCleanup is set to false. + * Get or set the localized control text that will be used for accessibility. * - * @param {Object} options - * Options to pass to {@link HTMLTrackElement} during creation. See - * {@link HTMLTrackElement} for object properties that you should use. + * > NOTE: This will come from the internal `menuButton_` element. * - * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be + * @param {string} [text] + * Control text for element. * - * @return {HtmlTrackElement} - * the HTMLTrackElement that was created and added - * to the HtmlTrackElementList and the remote - * TextTrackList + * @param {Element} [el=this.menuButton_.el()] + * Element to set the title on. * - * @deprecated The default value of the "manualCleanup" parameter will default - * to "false" in upcoming versions of Video.js + * @return {string} + * - The control text when getting */ - Player.prototype.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { - if (this.tech_) { - return this.tech_.addRemoteTextTrack(options, manualCleanup); - } + MenuButton.prototype.controlText = function controlText(text) { + var el = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.menuButton_.el(); + + return this.menuButton_.controlText(text, el); }; /** - * Remove a remote {@link TextTrack} from the respective - * {@link TextTrackList} and {@link HtmlTrackElementList}. + * Handle a click on a `MenuButton`. + * See {@link ClickableComponent#handleClick} for instances where this is called. * - * @param {Object} track - * Remote {@link TextTrack} to remove + * @param {EventTarget~Event} event + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. * - * @return {undefined} - * does not return anything + * @listens tap + * @listens click */ - Player.prototype.removeRemoteTextTrack = function removeRemoteTextTrack() { - var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, - _ref3$track = _ref3.track, - track = _ref3$track === undefined ? arguments[0] : _ref3$track; - - // 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 + MenuButton.prototype.handleClick = function handleClick(event) { + // When you click the button it adds focus, which will show the menu. + // So we'll remove focus when the mouse leaves the button. Focus is needed + // for tab navigation. - if (this.tech_) { - return this.tech_.removeRemoteTextTrack(track); + this.one(this.menu.contentEl(), 'mouseleave', bind(this, function (e) { + this.unpressButton(); + this.el_.blur(); + })); + if (this.buttonPressed_) { + this.unpressButton(); + } else { + this.pressButton(); } }; /** - * Gets available media playback quality metrics as specified by the W3C's Media - * Playback Quality API. - * - * @see [Spec]{@link https://wicg.github.io/media-playback-quality} - * - * @return {Object|undefined} - * An object with supported media playback quality metrics or undefined if there - * is no tech or the tech does not support it. + * Set the focus to the actual button, not to this element */ - Player.prototype.getVideoPlaybackQuality = function getVideoPlaybackQuality() { - return this.techGet_('getVideoPlaybackQuality'); + MenuButton.prototype.focus = function focus() { + this.menuButton_.focus(); }; /** - * Get video width - * - * @return {number} - * current video width + * Remove the focus from the actual button, not this element */ - Player.prototype.videoWidth = function videoWidth() { - return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0; + MenuButton.prototype.blur = function blur() { + this.menuButton_.blur(); }; /** - * Get video height + * This gets called when a `MenuButton` gains focus via a `focus` event. + * Turns on listening for `keydown` events. When they happen it + * calls `this.handleKeyPress`. * - * @return {number} - * current video height + * @param {EventTarget~Event} event + * The `focus` event that caused this function to be called. + * + * @listens focus */ - Player.prototype.videoHeight = function videoHeight() { - return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0; + MenuButton.prototype.handleFocus = function handleFocus() { + on(document_1, 'keydown', bind(this, this.handleKeyPress)); }; /** - * 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. + * Called when a `MenuButton` loses focus. Turns off the listener for + * `keydown` events. Which Stops `this.handleKeyPress` from getting called. * - * @param {string} [code] - * the language code to set the player to + * @param {EventTarget~Event} event + * The `blur` event that caused this function to be called. * - * @return {string} - * The current language code when getting + * @listens blur */ - Player.prototype.language = function language(code) { - if (code === undefined) { - return this.language_; - } - - this.language_ = String(code).toLowerCase(); + MenuButton.prototype.handleBlur = function handleBlur() { + off(document_1, 'keydown', bind(this, this.handleKeyPress)); }; /** - * 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 + * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See + * {@link ClickableComponent#handleKeyPress} for instances where this is called. * - * @return {Array} - * An array of of supported languages + * @param {EventTarget~Event} event + * The `keydown` event that caused this function to be called. + * + * @listens keydown */ - Player.prototype.languages = function languages() { - return (0, _mergeOptions2['default'])(Player.prototype.options_.languages, this.languages_); + MenuButton.prototype.handleKeyPress = function handleKeyPress(event) { + + // Escape (27) key or Tab (9) key unpress the 'button' + if (event.which === 27 || event.which === 9) { + if (this.buttonPressed_) { + this.unpressButton(); + } + // Don't preventDefault for Tab key - we still want to lose focus + if (event.which !== 9) { + event.preventDefault(); + // Set focus back to the menu button's button + this.menuButton_.el_.focus(); + } + // Up (38) key or Down (40) key press the 'button' + } else if (event.which === 38 || event.which === 40) { + if (!this.buttonPressed_) { + this.pressButton(); + event.preventDefault(); + } + } }; /** - * returns a JavaScript object reperesenting the current track - * information. **DOES not return it as JSON** + * Handle a `keydown` event on a sub-menu. The listener for this is added in + * the constructor. * - * @return {Object} - * Object representing the current of track info + * @param {EventTarget~Event} event + * Key press event + * + * @listens keydown */ - Player.prototype.toJSON = function toJSON() { - var options = (0, _mergeOptions2['default'])(this.options_); - var tracks = options.tracks; - - options.tracks = []; - - for (var i = 0; i < tracks.length; i++) { - var track = tracks[i]; + MenuButton.prototype.handleSubmenuKeyPress = function handleSubmenuKeyPress(event) { - // deep merge tracks and null out player so no circular references - track = (0, _mergeOptions2['default'])(track); - track.player = undefined; - options.tracks[i] = track; + // Escape (27) key or Tab (9) key unpress the 'button' + if (event.which === 27 || event.which === 9) { + if (this.buttonPressed_) { + this.unpressButton(); + } + // Don't preventDefault for Tab key - we still want to lose focus + if (event.which !== 9) { + event.preventDefault(); + // Set focus back to the menu button's button + this.menuButton_.el_.focus(); + } } - - return options; }; /** - * Creates a simple modal dialog (an instance of the {@link ModalDialog} - * component) that immediately overlays the player with arbitrary - * content and removes itself when closed. - * - * @param {string|Function|Element|Array|null} content - * Same as {@link 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 {@link ModalDialog}. - * - * @return {ModalDialog} - * the {@link ModalDialog} that was created + * Put the current `MenuButton` into a pressed state. */ - Player.prototype.createModal = function createModal(content, options) { - var _this7 = this; - - options = options || {}; - options.content = content || ''; - - var modal = new _modalDialog2['default'](this, options); + MenuButton.prototype.pressButton = function pressButton() { + if (this.enabled_) { + this.buttonPressed_ = true; + this.menu.lockShowing(); + this.menuButton_.el_.setAttribute('aria-expanded', 'true'); - this.addChild(modal); - modal.on('dispose', function () { - _this7.removeChild(modal); - }); + // set the focus into the submenu, except on iOS where it is resulting in + // undesired scrolling behavior when the player is in an iframe + if (IS_IOS && isInFrame()) { + // Return early so that the menu isn't focused + return; + } - modal.open(); - return modal; + this.menu.focus(); + } }; /** - * Gets tag settings - * - * @param {Element} tag - * The player tag - * - * @return {Object} - * An object containing all of the settings - * for a player tag + * Take the current `MenuButton` out of a pressed state. */ - Player.getTagSettings = function getTagSettings(tag) { - var baseOptions = { - sources: [], - tracks: [] - }; - - var tagOptions = Dom.getAttributes(tag); - var dataSetup = tagOptions['data-setup']; - - if (Dom.hasClass(tag, 'vjs-fluid')) { - tagOptions.fluid = true; - } - - // Check if data-setup attr exists. - if (dataSetup !== null) { - // Parse options JSON - // If empty string, make it a parsable json object. - var _safeParseTuple = (0, _tuple2['default'])(dataSetup || '{}'), - err = _safeParseTuple[0], - data = _safeParseTuple[1]; - - if (err) { - _log2['default'].error(err); - } - (0, _obj.assign)(tagOptions, data); + MenuButton.prototype.unpressButton = function unpressButton() { + if (this.enabled_) { + this.buttonPressed_ = false; + this.menu.unlockShowing(); + this.menuButton_.el_.setAttribute('aria-expanded', 'false'); } + }; - (0, _obj.assign)(baseOptions, tagOptions); + /** + * Disable the `MenuButton`. Don't allow it to be clicked. + */ - // 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(); + MenuButton.prototype.disable = function disable() { + this.unpressButton(); - if (childName === 'source') { - baseOptions.sources.push(Dom.getAttributes(child)); - } else if (childName === 'track') { - baseOptions.tracks.push(Dom.getAttributes(child)); - } - } - } + this.enabled_ = false; + this.addClass('vjs-disabled'); - return baseOptions; + this.menuButton_.disable(); }; /** - * Determine wether or not flexbox is supported - * - * @return {boolean} - * - true if flexbox is supported - * - false if flexbox is not supported + * Enable the `MenuButton`. Allow it to be clicked. */ - Player.prototype.flexNotSupported_ = function flexNotSupported_() { - var elem = _document2['default'].createElement('i'); + MenuButton.prototype.enable = function enable() { + this.enabled_ = true; + this.removeClass('vjs-disabled'); - // 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 || - // IE10-specific (2012 flex spec) - 'msFlexOrder' in elem.style); + this.menuButton_.enable(); }; - return Player; -}(_component2['default']); + return MenuButton; +}(Component); -/** - * Get the {@link VideoTrackList} - * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist - * - * @return {VideoTrackList} - * the current video track list - * - * @method Player.prototype.videoTracks - */ +Component.registerComponent('MenuButton', MenuButton); /** - * Get the {@link AudioTrackList} - * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist - * - * @return {AudioTrackList} - * the current audio track list - * - * @method Player.prototype.audioTracks + * @file track-button.js */ - /** - * Get the {@link TextTrackList} - * - * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks - * - * @return {TextTrackList} - * the current text track list + * The base class for buttons that toggle specific track types (e.g. subtitles). * - * @method Player.prototype.textTracks + * @extends MenuButton */ -/** - * Get the remote {@link TextTrackList} - * - * @return {TextTrackList} - * The current remote text track list - * - * @method Player.prototype.textTracks - */ +var TrackButton = function (_MenuButton) { + inherits(TrackButton, _MenuButton); -/** - * Get the remote {@link HtmlTrackElementList} tracks. - * - * @return {HtmlTrackElementList} - * The current remote text track element list - * - * @method Player.prototype.remoteTextTrackEls - */ + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function TrackButton(player, options) { + classCallCheck(this, TrackButton); + + var tracks = options.tracks; -_trackTypes.ALL.names.forEach(function (name) { - var props = _trackTypes.ALL[name]; + var _this = possibleConstructorReturn(this, _MenuButton.call(this, player, options)); - Player.prototype[props.getterName] = function () { - if (this.tech_) { - return this.tech_[props.getterName](); + if (_this.items.length <= 1) { + _this.hide(); } - // if we have not yet loadTech_, we create {video,audio,text}Tracks_ - // these will be passed to the tech during loading - this[props.privateName] = this[props.privateName] || new props.ListClass(); - return this[props.privateName]; - }; -}); + if (!tracks) { + return possibleConstructorReturn(_this); + } + + var updateHandler = bind(_this, _this.update); + + tracks.addEventListener('removetrack', updateHandler); + tracks.addEventListener('addtrack', updateHandler); + _this.player_.on('ready', updateHandler); + + _this.player_.on('dispose', function () { + tracks.removeEventListener('removetrack', updateHandler); + tracks.removeEventListener('addtrack', updateHandler); + }); + return _this; + } + + return TrackButton; +}(MenuButton); + +Component.registerComponent('TrackButton', TrackButton); /** - * Global player list + * @file menu-item.js + */ +/** + * The component for a menu item. `
  • ` * - * @type {Object} + * @extends ClickableComponent */ -Player.players = {}; -var navigator = _window2['default'].navigator; +var MenuItem = function (_ClickableComponent) { + inherits(MenuItem, _ClickableComponent); -/* - * 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: _tech2['default'].defaultTechOrder_, + /** + * Creates an instance of the this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options={}] + * The key/value store of player options. + * + */ + function MenuItem(player, options) { + classCallCheck(this, MenuItem); - html5: {}, - flash: {}, + var _this = possibleConstructorReturn(this, _ClickableComponent.call(this, player, options)); - // default inactivity timeout - inactivityTimeout: 2000, + _this.selectable = options.selectable; + _this.isSelected_ = options.selected || false; - // default playback rates - playbackRates: [], - // Add playback rate selection by adding rates - // 'playbackRates': [0.5, 1, 1.5, 2], + _this.selected(_this.isSelected_); - // Included control sets - children: ['mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'controlBar', 'errorDisplay', 'textTrackSettings'], + 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'); + } + return _this; + } - language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en', + /** + * Create the `MenuItem's DOM element + * + * @param {string} [type=li] + * Element's node type, not actually used, always set to `li`. + * + * @param {Object} [props={}] + * An object of properties that should be set on the element + * + * @param {Object} [attrs={}] + * An object of attributes that should be set on the element + * + * @return {Element} + * The element that gets created. + */ - // 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.' -}; + MenuItem.prototype.createEl = function createEl(type, props, attrs) { + // The control is textual, not just an icon + this.nonIconControl = true; -[ -/** - * 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 Player#ended - */ -'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 Player#seeking - */ -'seeking', -/** - * Returns the TimeRanges of the media that are currently available - * for seeking to. - * - * @return {TimeRanges} the seekable intervals of the media timeline - * @method Player#seekable - */ -'seekable', -/** - * 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 Player#networkState - */ -'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 Player#readyState - */ -'readyState'].forEach(function (fn) { - Player.prototype[fn] = function () { - return this.techGet_(fn); + return _ClickableComponent.prototype.createEl.call(this, 'li', assign({ + className: 'vjs-menu-item', + innerHTML: '' + this.localize(this.options_.label) + '', + tabIndex: -1 + }, props), attrs); }; -}); -TECH_EVENTS_RETRIGGER.forEach(function (event) { - Player.prototype['handleTech' + (0, _toTitleCase2['default'])(event) + '_'] = function () { - return this.trigger(event); + /** + * Any click on a `MenuItem` puts int into the selected state. + * See {@link ClickableComponent#handleClick} for instances where this is called. + * + * @param {EventTarget~Event} event + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click + */ + + + MenuItem.prototype.handleClick = function handleClick(event) { + this.selected(true); }; -}); -/** - * Fired when the player has initial duration and dimension information - * - * @event Player#loadedmetadata - * @type {EventTarget~Event} - */ + /** + * Set the state for this menu item as selected or not. + * + * @param {boolean} selected + * if the menu item is selected or not + */ -/** - * Fired when the player has downloaded data at the current playback position - * - * @event Player#loadeddata - * @type {EventTarget~Event} - */ -/** - * 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 Player#timeupdate - * @type {EventTarget~Event} - */ + 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'); + this.isSelected_ = true; + } else { + this.removeClass('vjs-selected'); + this.el_.setAttribute('aria-checked', 'false'); + // Indicate un-selected state to screen reader + this.controlText(''); + this.isSelected_ = false; + } + } + }; -/** - * Fired when the volume changes - * - * @event Player#volumechange - * @type {EventTarget~Event} - */ + return MenuItem; +}(ClickableComponent); + +Component.registerComponent('MenuItem', MenuItem); /** - * Reports whether or not a player has a plugin available. - * - * This does not report whether or not the plugin has ever been initialized - * on this player. For that, [usingPlugin]{@link Player#usingPlugin}. - * - * @method Player#hasPlugin - * @param {string} name - * The name of a plugin. - * - * @return {boolean} - * Whether or not this player has the requested plugin available. + * @file text-track-menu-item.js */ - /** - * Reports whether or not a player is using a plugin by name. - * - * For basic plugins, this only reports whether the plugin has _ever_ been - * initialized on this player. - * - * @method Player#usingPlugin - * @param {string} name - * The name of a plugin. + * The specific menu item type for selecting a language within a text track kind * - * @return {boolean} - * Whether or not this player is using the requested plugin. + * @extends MenuItem */ -_component2['default'].registerComponent('Player', Player); -exports['default'] = Player; +var TextTrackMenuItem = function (_MenuItem) { + inherits(TextTrackMenuItem, _MenuItem); + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function TextTrackMenuItem(player, options) { + classCallCheck(this, TextTrackMenuItem); + + var track = options.track; + var tracks = player.textTracks(); + + // Modify options for parent MenuItem class's init. + options.label = track.label || track.language || 'Unknown'; + options.selected = track.mode === 'showing'; + + var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options)); + + _this.track = track; + var changeHandler = function changeHandler() { + for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + _this.handleTracksChange.apply(_this, args); + }; + var selectedLanguageChangeHandler = function selectedLanguageChangeHandler() { + for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + args[_key2] = arguments[_key2]; + } + + _this.handleSelectedLanguageChange.apply(_this, args); + }; + + player.on(['loadstart', 'texttrackchange'], changeHandler); + tracks.addEventListener('change', changeHandler); + tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler); + _this.on('dispose', function () { + player.off(['loadstart', 'texttrackchange'], changeHandler); + tracks.removeEventListener('change', changeHandler); + tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler); + }); + + // iOS7 doesn't dispatch change events to TextTrackLists when an + // associated track's mode changes. Without something like + // Object.observe() (also not present on iOS7), it's not + // possible to detect changes to the mode attribute and polyfill + // the change event. As a poor substitute, we manually dispatch + // change events whenever the controls modify the mode. + if (tracks.onchange === undefined) { + var event = void 0; + + _this.on(['tap', 'click'], function () { + if (_typeof(window_1.Event) !== 'object') { + // Android 2.3 throws an Illegal Constructor error for window.Event + try { + event = new window_1.Event('change'); + } catch (err) { + // continue regardless of error + } + } + + if (!event) { + event = document_1.createEvent('Event'); + event.initEvent('change', true, true); + } + + tracks.dispatchEvent(event); + }); + } -},{"1":1,"100":100,"102":102,"103":103,"4":4,"44":44,"47":47,"48":48,"49":49,"5":5,"53":53,"55":55,"58":58,"61":61,"62":62,"63":63,"64":64,"70":70,"71":71,"73":73,"77":77,"8":8,"81":81,"82":82,"85":85,"86":86,"87":87,"88":88,"90":90,"91":91,"92":92,"93":93,"94":94,"95":95,"96":96,"99":99}],57:[function(_dereq_,module,exports){ -'use strict'; + // set the default state based on current tracks + _this.handleTracksChange(); + return _this; + } -exports.__esModule = true; + /** + * This gets called when an `TextTrackMenuItem` is "clicked". See + * {@link ClickableComponent} for more detailed information on what a click can be. + * + * @param {EventTarget~Event} event + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click + */ -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; -var _evented = _dereq_(53); + TextTrackMenuItem.prototype.handleClick = function handleClick(event) { + var kind = this.track.kind; + var kinds = this.track.kinds; + var tracks = this.player_.textTracks(); -var _evented2 = _interopRequireDefault(_evented); + if (!kinds) { + kinds = [kind]; + } -var _stateful = _dereq_(54); + _MenuItem.prototype.handleClick.call(this, event); -var _stateful2 = _interopRequireDefault(_stateful); + if (!tracks) { + return; + } -var _events = _dereq_(86); + for (var i = 0; i < tracks.length; i++) { + var track = tracks[i]; -var Events = _interopRequireWildcard(_events); + if (track === this.track && kinds.indexOf(track.kind) > -1) { + if (track.mode !== 'showing') { + track.mode = 'showing'; + } + } else if (track.mode !== 'disabled') { + track.mode = 'disabled'; + } + } + }; -var _fn = _dereq_(88); + /** + * Handle text track list change + * + * @param {EventTarget~Event} event + * The `change` event that caused this function to be called. + * + * @listens TextTrackList#change + */ -var Fn = _interopRequireWildcard(_fn); -var _log = _dereq_(91); + TextTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) { + var shouldBeSelected = this.track.mode === 'showing'; -var _log2 = _interopRequireDefault(_log); + // Prevent redundant selected() calls because they may cause + // screen readers to read the appended control text unnecessarily + if (shouldBeSelected !== this.isSelected_) { + this.selected(shouldBeSelected); + } + }; -var _player = _dereq_(56); + TextTrackMenuItem.prototype.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) { + if (this.track.mode === 'showing') { + var selectedLanguage = this.player_.cache_.selectedLanguage; -var _player2 = _interopRequireDefault(_player); + // Don't replace the kind of track across the same language + if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) { + return; + } -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; } } + this.player_.cache_.selectedLanguage = { + enabled: true, + language: this.track.language, + kind: this.track.kind + }; + } + }; -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + TextTrackMenuItem.prototype.dispose = function dispose() { + // remove reference to track object on dispose + this.track = null; -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /** - * @file plugin.js - */ + _MenuItem.prototype.dispose.call(this); + }; + return TextTrackMenuItem; +}(MenuItem); -/** - * The base plugin name. - * - * @private - * @constant - * @type {string} - */ -var BASE_PLUGIN_NAME = 'plugin'; +Component.registerComponent('TextTrackMenuItem', TextTrackMenuItem); /** - * The key on which a player's active plugins cache is stored. - * - * @private - * @constant - * @type {string} + * @file off-text-track-menu-item.js */ -var PLUGIN_CACHE_KEY = 'activePlugins_'; - /** - * Stores registered plugins in a private space. + * A special menu item for turning of a specific type of text track * - * @private - * @type {Object} + * @extends TextTrackMenuItem */ -var pluginStorage = {}; -/** - * Reports whether or not a plugin has been registered. - * - * @private - * @param {string} name - * The name of a plugin. - * - * @returns {boolean} - * Whether or not the plugin has been registered. - */ -var pluginExists = function pluginExists(name) { - return pluginStorage.hasOwnProperty(name); -}; +var OffTextTrackMenuItem = function (_TextTrackMenuItem) { + inherits(OffTextTrackMenuItem, _TextTrackMenuItem); -/** - * Get a single registered plugin by name. - * - * @private - * @param {string} name - * The name of a plugin. - * - * @returns {Function|undefined} - * The plugin (or undefined). - */ -var getPlugin = function getPlugin(name) { - return pluginExists(name) ? pluginStorage[name] : undefined; -}; + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function OffTextTrackMenuItem(player, options) { + classCallCheck(this, OffTextTrackMenuItem); -/** - * Marks a plugin as "active" on a player. - * - * Also, ensures that the player has an object for tracking active plugins. - * - * @private - * @param {Player} player - * A Video.js player instance. - * - * @param {string} name - * The name of a plugin. - */ -var markPluginAsActive = function markPluginAsActive(player, name) { - player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {}; - player[PLUGIN_CACHE_KEY][name] = true; -}; + // Create pseudo track info + // Requires options['kind'] + options.track = { + player: player, + kind: options.kind, + kinds: options.kinds, + 'default': false, + mode: 'disabled' + }; -/** - * Triggers a pair of plugin setup events. - * - * @private - * @param {Player} player - * A Video.js player instance. - * - * @param {Plugin~PluginEventHash} hash - * A plugin event hash. - * - * @param {Boolean} [before] - * If true, prefixes the event name with "before". In other words, - * use this to trigger "beforepluginsetup" instead of "pluginsetup". - */ -var triggerSetupEvent = function triggerSetupEvent(player, hash, before) { - var eventName = (before ? 'before' : '') + 'pluginsetup'; + if (!options.kinds) { + options.kinds = [options.kind]; + } - player.trigger(eventName, hash); - player.trigger(eventName + ':' + hash.name, hash); -}; + if (options.label) { + options.track.label = options.label; + } else { + options.track.label = options.kinds.join(' and ') + ' off'; + } -/** - * Takes a basic plugin function and returns a wrapper function which marks - * on the player that the plugin has been activated. - * - * @private - * @param {string} name - * The name of the plugin. - * - * @param {Function} plugin - * The basic plugin. - * - * @returns {Function} - * A wrapper function for the given plugin. - */ -var createBasicPlugin = function createBasicPlugin(name, plugin) { - var basicPluginWrapper = function basicPluginWrapper() { + // MenuItem is selectable + options.selectable = true; - // We trigger the "beforepluginsetup" and "pluginsetup" events on the player - // regardless, but we want the hash to be consistent with the hash provided - // for advanced plugins. - // - // The only potentially counter-intuitive thing here is the `instance` in - // the "pluginsetup" event is the value returned by the `plugin` function. - triggerSetupEvent(this, { name: name, plugin: plugin, instance: null }, true); + return possibleConstructorReturn(this, _TextTrackMenuItem.call(this, player, options)); + } - var instance = plugin.apply(this, arguments); + /** + * Handle text track change + * + * @param {EventTarget~Event} event + * The event that caused this function to run + */ - markPluginAsActive(this, name); - triggerSetupEvent(this, { name: name, plugin: plugin, instance: instance }); - return instance; - }; + OffTextTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) { + var tracks = this.player().textTracks(); + var shouldBeSelected = true; - Object.keys(plugin).forEach(function (prop) { - basicPluginWrapper[prop] = plugin[prop]; - }); + for (var i = 0, l = tracks.length; i < l; i++) { + var track = tracks[i]; - return basicPluginWrapper; -}; + if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') { + shouldBeSelected = false; + break; + } + } -/** - * Takes a plugin sub-class and returns a factory function for generating - * instances of it. - * - * This factory function will replace itself with an instance of the requested - * sub-class of Plugin. - * - * @private - * @param {string} name - * The name of the plugin. - * - * @param {Plugin} PluginSubClass - * The advanced plugin. - * - * @returns {Function} - */ -var createPluginFactory = function createPluginFactory(name, PluginSubClass) { + // Prevent redundant selected() calls because they may cause + // screen readers to read the appended control text unnecessarily + if (shouldBeSelected !== this.isSelected_) { + this.selected(shouldBeSelected); + } + }; - // Add a `name` property to the plugin prototype so that each plugin can - // refer to itself by name. - PluginSubClass.prototype.name = name; + OffTextTrackMenuItem.prototype.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) { + var tracks = this.player().textTracks(); + var allHidden = true; - return function () { - triggerSetupEvent(this, { name: name, plugin: PluginSubClass, instance: null }, true); + for (var i = 0, l = tracks.length; i < l; i++) { + var track = tracks[i]; - for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { - args[_key] = arguments[_key]; + if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') { + allHidden = false; + break; + } } - var instance = new (Function.prototype.bind.apply(PluginSubClass, [null].concat([this].concat(args))))(); - - // The plugin is replaced by a function that returns the current instance. - this[name] = function () { - return instance; - }; + if (allHidden) { + this.player_.cache_.selectedLanguage = { + enabled: false + }; + } + }; - triggerSetupEvent(this, instance.getEventHash()); + return OffTextTrackMenuItem; +}(TextTrackMenuItem); - return instance; - }; -}; +Component.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem); /** - * Parent class for all advanced plugins. + * @file text-track-button.js + */ +/** + * The base class for buttons that toggle specific text track types (e.g. subtitles) * - * @mixes module:evented~EventedMixin - * @mixes module:stateful~StatefulMixin - * @fires Player#beforepluginsetup - * @fires Player#beforepluginsetup:$name - * @fires Player#pluginsetup - * @fires Player#pluginsetup:$name - * @listens Player#dispose - * @throws {Error} - * If attempting to instantiate the base {@link Plugin} class - * directly instead of via a sub-class. + * @extends MenuButton */ -var Plugin = function () { +var TextTrackButton = function (_TrackButton) { + inherits(TextTrackButton, _TrackButton); /** * Creates an instance of this class. * - * Sub-classes should call `super` to ensure plugins are properly initialized. - * * @param {Player} player - * A Video.js player instance. + * The `Player` that this class should be attached to. + * + * @param {Object} [options={}] + * The key/value store of player options. */ - function Plugin(player) { - _classCallCheck(this, Plugin); - - if (this.constructor === Plugin) { - throw new Error('Plugin must be sub-classed; not directly instantiated.'); - } - - this.player = player; - - // Make this object evented, but remove the added `trigger` method so we - // use the prototype version instead. - (0, _evented2['default'])(this); - delete this.trigger; - - (0, _stateful2['default'])(this, this.constructor.defaultState); - markPluginAsActive(player, this.name); + function TextTrackButton(player) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + classCallCheck(this, TextTrackButton); - // Auto-bind the dispose method so we can use it as a listener and unbind - // it later easily. - this.dispose = Fn.bind(this, this.dispose); + options.tracks = player.textTracks(); - // If the player is disposed, dispose the plugin. - player.on('dispose', this.dispose); + return possibleConstructorReturn(this, _TrackButton.call(this, player, options)); } /** - * Each event triggered by plugins includes a hash of additional data with - * conventional properties. - * - * This returns that object or mutates an existing hash. + * Create a menu item for each text track * - * @param {Object} [hash={}] - * An object to be used as event an event hash. + * @param {TextTrackMenuItem[]} [items=[]] + * Existing array of items to use during creation * - * @returns {Plugin~PluginEventHash} - * An event hash object with provided properties mixed-in. + * @return {TextTrackMenuItem[]} + * Array of menu items that were created */ - Plugin.prototype.getEventHash = function getEventHash() { - var hash = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + TextTrackButton.prototype.createItems = function createItems() { + var items = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + var TrackMenuItem = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : TextTrackMenuItem; - hash.name = this.name; - hash.plugin = this.constructor; - hash.instance = this; - return hash; - }; - /** - * Triggers an event on the plugin object and overrides - * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}. - * - * @param {string|Object} event - * An event type or an object with a type property. - * - * @param {Object} [hash={}] - * Additional data hash to merge with a - * {@link Plugin~PluginEventHash|PluginEventHash}. - * - * @returns {boolean} - * Whether or not default was prevented. - */ + // Label is an overide for the [track] off label + // USed to localise captions/subtitles + var label = void 0; + + if (this.label_) { + label = this.label_ + ' off'; + } + // Add an OFF menu item to turn all tracks off + items.push(new OffTextTrackMenuItem(this.player_, { + kinds: this.kinds_, + kind: this.kind_, + label: label + })); + + this.hideThreshold_ += 1; + var tracks = this.player_.textTracks(); - Plugin.prototype.trigger = function trigger(event) { - var hash = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + if (!Array.isArray(this.kinds_)) { + this.kinds_ = [this.kind_]; + } + + for (var i = 0; i < tracks.length; i++) { + var track = tracks[i]; + + // only add tracks that are of an appropriate kind and have a label + if (this.kinds_.indexOf(track.kind) > -1) { - return Events.trigger(this.eventBusEl_, event, this.getEventHash(hash)); + var item = new TrackMenuItem(this.player_, { + track: track, + // MenuItem is selectable + selectable: true + }); + + item.addClass('vjs-' + track.kind + '-menu-item'); + items.push(item); + } + } + + return items; }; - /** - * Handles "statechanged" events on the plugin. No-op by default, override by - * subclassing. - * - * @abstract - * @param {Event} e - * An event object provided by a "statechanged" event. - * - * @param {Object} e.changes - * An object describing changes that occurred with the "statechanged" - * event. - */ + return TextTrackButton; +}(TrackButton); +Component.registerComponent('TextTrackButton', TextTrackButton); - Plugin.prototype.handleStateChanged = function handleStateChanged(e) {}; +/** + * @file chapters-track-menu-item.js + */ +/** + * The chapter track menu item + * + * @extends MenuItem + */ + +var ChaptersTrackMenuItem = function (_MenuItem) { + inherits(ChaptersTrackMenuItem, _MenuItem); /** - * Disposes a plugin. + * Creates an instance of this class. * - * Subclasses can override this if they want, but for the sake of safety, - * it's probably best to subscribe the "dispose" event. + * @param {Player} player + * The `Player` that this class should be attached to. * - * @fires Plugin#dispose + * @param {Object} [options] + * The key/value store of player options. */ + function ChaptersTrackMenuItem(player, options) { + classCallCheck(this, ChaptersTrackMenuItem); + var track = options.track; + var cue = options.cue; + var currentTime = player.currentTime(); - Plugin.prototype.dispose = function dispose() { - var name = this.name, - player = this.player; + // Modify options for parent MenuItem class's init. + options.selectable = true; + options.label = cue.text; + options.selected = cue.startTime <= currentTime && currentTime < cue.endTime; - /** - * Signals that a advanced plugin is about to be disposed. - * - * @event Plugin#dispose - * @type {EventTarget~Event} - */ + var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options)); - this.trigger('dispose'); - this.off(); - player.off('dispose', this.dispose); + _this.track = track; + _this.cue = cue; + track.addEventListener('cuechange', bind(_this, _this.update)); + return _this; + } - // Eliminate any possible sources of leaking memory by clearing up - // references between the player and the plugin instance and nulling out - // the plugin's state and replacing methods with a function that throws. - player[PLUGIN_CACHE_KEY][name] = false; - this.player = this.state = null; + /** + * This gets called when an `ChaptersTrackMenuItem` is "clicked". See + * {@link ClickableComponent} for more detailed information on what a click can be. + * + * @param {EventTarget~Event} [event] + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click + */ - // Finally, replace the plugin name on the player with a new factory - // function, so that the plugin is ready to be set up again. - player[name] = createPluginFactory(name, pluginStorage[name]); + + ChaptersTrackMenuItem.prototype.handleClick = function handleClick(event) { + _MenuItem.prototype.handleClick.call(this); + this.player_.currentTime(this.cue.startTime); + this.update(this.cue.startTime); }; /** - * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`). + * Update chapter menu item * - * @param {string|Function} plugin - * If a string, matches the name of a plugin. If a function, will be - * tested directly. + * @param {EventTarget~Event} [event] + * The `cuechange` event that caused this function to run. * - * @returns {boolean} - * Whether or not a plugin is a basic plugin. + * @listens TextTrack#cuechange */ - Plugin.isBasic = function isBasic(plugin) { - var p = typeof plugin === 'string' ? getPlugin(plugin) : plugin; + ChaptersTrackMenuItem.prototype.update = function update(event) { + var cue = this.cue; + var currentTime = this.player_.currentTime(); - return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype); + // vjs.log(currentTime, cue.startTime); + this.selected(cue.startTime <= currentTime && currentTime < cue.endTime); }; + return ChaptersTrackMenuItem; +}(MenuItem); + +Component.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem); + +/** + * @file chapters-button.js + */ +/** + * The button component for toggling and selecting chapters + * Chapters act much differently than other text tracks + * Cues are navigation vs. other tracks of alternative languages + * + * @extends TextTrackButton + */ + +var ChaptersButton = function (_TextTrackButton) { + inherits(ChaptersButton, _TextTrackButton); + /** - * Register a Video.js plugin. + * Creates an instance of this class. * - * @param {string} name - * The name of the plugin to be registered. Must be a string and - * must not match an existing plugin or a method on the `Player` - * prototype. + * @param {Player} player + * The `Player` that this class should be attached to. * - * @param {Function} plugin - * A sub-class of `Plugin` or a function for basic plugins. + * @param {Object} [options] + * The key/value store of player options. * - * @returns {Function} - * For advanced plugins, a factory function for that plugin. For - * basic plugins, a wrapper function that initializes the plugin. + * @param {Component~ReadyCallback} [ready] + * The function to call when this function is ready. */ + function ChaptersButton(player, options, ready) { + classCallCheck(this, ChaptersButton); + return possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready)); + } + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ - Plugin.registerPlugin = function registerPlugin(name, plugin) { - if (typeof name !== 'string') { - throw new Error('Illegal plugin name, "' + name + '", must be a string, was ' + (typeof name === 'undefined' ? 'undefined' : _typeof(name)) + '.'); - } - - if (pluginExists(name)) { - _log2['default'].warn('A plugin named "' + name + '" already exists. You may want to avoid re-registering plugins!'); - } else if (_player2['default'].prototype.hasOwnProperty(name)) { - throw new Error('Illegal plugin name, "' + name + '", cannot share a name with an existing player method!'); - } - - if (typeof plugin !== 'function') { - throw new Error('Illegal plugin for "' + name + '", must be a function, was ' + (typeof plugin === 'undefined' ? 'undefined' : _typeof(plugin)) + '.'); - } - - pluginStorage[name] = plugin; - // Add a player prototype method for all sub-classed plugins (but not for - // the base Plugin class). - if (name !== BASE_PLUGIN_NAME) { - if (Plugin.isBasic(plugin)) { - _player2['default'].prototype[name] = createBasicPlugin(name, plugin); - } else { - _player2['default'].prototype[name] = createPluginFactory(name, plugin); - } - } + ChaptersButton.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-chapters-button ' + _TextTrackButton.prototype.buildCSSClass.call(this); + }; - return plugin; + ChaptersButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { + return 'vjs-chapters-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); }; /** - * De-register a Video.js plugin. + * Update the menu based on the current state of its items. * - * @param {string} name - * The name of the plugin to be deregistered. + * @param {EventTarget~Event} [event] + * An event that triggered this function to run. + * + * @listens TextTrackList#addtrack + * @listens TextTrackList#removetrack + * @listens TextTrackList#change */ - Plugin.deregisterPlugin = function deregisterPlugin(name) { - if (name === BASE_PLUGIN_NAME) { - throw new Error('Cannot de-register base plugin.'); - } - if (pluginExists(name)) { - delete pluginStorage[name]; - delete _player2['default'].prototype[name]; + ChaptersButton.prototype.update = function update(event) { + if (!this.track_ || event && (event.type === 'addtrack' || event.type === 'removetrack')) { + this.setTrack(this.findChaptersTrack()); } + _TextTrackButton.prototype.update.call(this); }; /** - * Gets an object containing multiple Video.js plugins. - * - * @param {Array} [names] - * If provided, should be an array of plugin names. Defaults to _all_ - * plugin names. + * Set the currently selected track for the chapters button. * - * @returns {Object|undefined} - * An object containing plugin(s) associated with their name(s) or - * `undefined` if no matching plugins exist). + * @param {TextTrack} track + * The new track to select. Nothing will change if this is the currently selected + * track. */ - Plugin.getPlugins = function getPlugins() { - var names = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Object.keys(pluginStorage); + ChaptersButton.prototype.setTrack = function setTrack(track) { + if (this.track_ === track) { + return; + } - var result = void 0; + if (!this.updateHandler_) { + this.updateHandler_ = this.update.bind(this); + } - names.forEach(function (name) { - var plugin = getPlugin(name); + // here this.track_ refers to the old track instance + if (this.track_) { + var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); - if (plugin) { - result = result || {}; - result[name] = plugin; + if (remoteTextTrackEl) { + remoteTextTrackEl.removeEventListener('load', this.updateHandler_); } - }); - return result; - }; + this.track_ = null; + } - /** - * Gets a plugin's version, if available - * - * @param {string} name - * The name of a plugin. - * - * @returns {string} - * The plugin's version or an empty string. - */ + this.track_ = track; + // here this.track_ refers to the new track instance + if (this.track_) { + this.track_.mode = 'hidden'; - Plugin.getPluginVersion = function getPluginVersion(name) { - var plugin = getPlugin(name); + var _remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); - return plugin && plugin.VERSION || ''; + if (_remoteTextTrackEl) { + _remoteTextTrackEl.addEventListener('load', this.updateHandler_); + } + } }; - return Plugin; -}(); - -/** - * Gets a plugin by name if it exists. - * - * @static - * @method getPlugin - * @memberOf Plugin - * @param {string} name - * The name of a plugin. - * - * @returns {Function|undefined} - * The plugin (or `undefined`). - */ - - -Plugin.getPlugin = getPlugin; - -/** - * The name of the base plugin class as it is registered. - * - * @type {string} - */ -Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME; - -Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin); - -/** - * Documented in player.js - * - * @ignore - */ -_player2['default'].prototype.usingPlugin = function (name) { - return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true; -}; + /** + * Find the track object that is currently in use by this ChaptersButton + * + * @return {TextTrack|undefined} + * The current track or undefined if none was found. + */ -/** - * Documented in player.js - * - * @ignore - */ -_player2['default'].prototype.hasPlugin = function (name) { - return !!pluginExists(name); -}; -exports['default'] = Plugin; + ChaptersButton.prototype.findChaptersTrack = function findChaptersTrack() { + var tracks = this.player_.textTracks() || []; -/** - * Signals that a plugin is about to be set up on a player. - * - * @event Player#beforepluginsetup - * @type {Plugin~PluginEventHash} - */ + for (var i = tracks.length - 1; i >= 0; i--) { + // We will always choose the last track as our chaptersTrack + var track = tracks[i]; -/** - * Signals that a plugin is about to be set up on a player - by name. The name - * is the name of the plugin. - * - * @event Player#beforepluginsetup:$name - * @type {Plugin~PluginEventHash} - */ + if (track.kind === this.kind_) { + return track; + } + } + }; -/** - * Signals that a plugin has just been set up on a player. - * - * @event Player#pluginsetup - * @type {Plugin~PluginEventHash} - */ + /** + * Get the caption for the ChaptersButton based on the track label. This will also + * use the current tracks localized kind as a fallback if a label does not exist. + * + * @return {string} + * The tracks current label or the localized track kind. + */ -/** - * Signals that a plugin has just been set up on a player - by name. The name - * is the name of the plugin. - * - * @event Player#pluginsetup:$name - * @type {Plugin~PluginEventHash} - */ -/** - * @typedef {Object} Plugin~PluginEventHash - * - * @property {string} instance - * For basic plugins, the return value of the plugin function. For - * advanced plugins, the plugin instance on which the event is fired. - * - * @property {string} name - * The name of the plugin. - * - * @property {string} plugin - * For basic plugins, the plugin function. For advanced plugins, the - * plugin class/constructor. - */ + ChaptersButton.prototype.getMenuCaption = function getMenuCaption() { + if (this.track_ && this.track_.label) { + return this.track_.label; + } + return this.localize(toTitleCase(this.kind_)); + }; -},{"53":53,"54":54,"56":56,"86":86,"88":88,"91":91}],58:[function(_dereq_,module,exports){ -'use strict'; + /** + * Create menu from chapter track + * + * @return {Menu} + * New menu for the chapter buttons + */ -exports.__esModule = true; -var _clickableComponent = _dereq_(3); + ChaptersButton.prototype.createMenu = function createMenu() { + this.options_.title = this.getMenuCaption(); + return _TextTrackButton.prototype.createMenu.call(this); + }; -var _clickableComponent2 = _interopRequireDefault(_clickableComponent); + /** + * Create a menu item for each text track + * + * @return {TextTrackMenuItem[]} + * Array of menu items + */ -var _component = _dereq_(5); -var _component2 = _interopRequireDefault(_component); + ChaptersButton.prototype.createItems = function createItems() { + var items = []; -var _fn = _dereq_(88); + if (!this.track_) { + return items; + } -var Fn = _interopRequireWildcard(_fn); + var cues = this.track_.cues; -var _dom = _dereq_(85); + if (!cues) { + return items; + } -var Dom = _interopRequireWildcard(_dom); + for (var i = 0, l = cues.length; i < l; i++) { + var cue = cues[i]; + var mi = new ChaptersTrackMenuItem(this.player_, { track: this.track_, cue: cue }); -var _browser = _dereq_(81); + items.push(mi); + } -var browser = _interopRequireWildcard(_browser); + return items; + }; -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 ChaptersButton; +}(TextTrackButton); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } +/** + * `kind` of TextTrack to look for to associate it with this menu. + * + * @type {string} + * @private + */ -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } +ChaptersButton.prototype.kind_ = 'chapters'; -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; } /** - * @file poster-image.js - */ +/** + * The text that should display over the `ChaptersButton`s controls. Added for localization. + * + * @type {string} + * @private + */ +ChaptersButton.prototype.controlText_ = 'Chapters'; +Component.registerComponent('ChaptersButton', ChaptersButton); /** - * A `ClickableComponent` that handles showing the poster image for the player. + * @file descriptions-button.js + */ +/** + * The button component for toggling and selecting descriptions * - * @extends ClickableComponent + * @extends TextTrackButton */ -var PosterImage = function (_ClickableComponent) { - _inherits(PosterImage, _ClickableComponent); + +var DescriptionsButton = function (_TextTrackButton) { + inherits(DescriptionsButton, _TextTrackButton); /** - * Create an instance of this class. + * Creates an instance of this class. * * @param {Player} player - * The `Player` that this class should attach to. + * The `Player` that this class should be attached to. * * @param {Object} [options] * The key/value store of player options. + * + * @param {Component~ReadyCallback} [ready] + * The function to call when this component is ready. */ - function PosterImage(player, options) { - _classCallCheck(this, PosterImage); + function DescriptionsButton(player, options, ready) { + classCallCheck(this, DescriptionsButton); - var _this = _possibleConstructorReturn(this, _ClickableComponent.call(this, player, options)); + var _this = possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready)); - _this.update(); - player.on('posterchange', Fn.bind(_this, _this.update)); + var tracks = player.textTracks(); + var changeHandler = bind(_this, _this.handleTracksChange); + + tracks.addEventListener('change', changeHandler); + _this.on('dispose', function () { + tracks.removeEventListener('change', changeHandler); + }); return _this; } /** - * Clean up and dispose of the `PosterImage`. - */ - - - PosterImage.prototype.dispose = function dispose() { - this.player().off('posterchange', this.update); - _ClickableComponent.prototype.dispose.call(this); - }; - - /** - * Create the `PosterImage`s DOM element. + * Handle text track change * - * @return {Element} - * The element that gets created. + * @param {EventTarget~Event} event + * The event that caused this function to run + * + * @listens TextTrackList#change */ - PosterImage.prototype.createEl = function createEl() { - var el = Dom.createEl('div', { - className: 'vjs-poster', + DescriptionsButton.prototype.handleTracksChange = function handleTracksChange(event) { + var tracks = this.player().textTracks(); + var disabled = false; - // Don't want poster to be tabbable. - tabIndex: -1 - }); + // Check whether a track of a different kind is showing + for (var i = 0, l = tracks.length; i < l; i++) { + var track = tracks[i]; - // 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_); + if (track.kind !== this.kind_ && track.mode === 'showing') { + disabled = true; + break; + } } - return el; + // If another track is showing, disable this menu button + if (disabled) { + this.disable(); + } else { + this.enable(); + } }; /** - * An {@link EventTarget~EventListener} for {@link Player#posterchange} events. - * - * @listens Player#posterchange - * - * @param {EventTarget~Event} [event] - * The `Player#posterchange` event that triggered this function. - */ - - - PosterImage.prototype.update = function update(event) { - 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 source of the `PosterImage` depending on the display method. + * Builds the default DOM `className`. * - * @param {string} url - * The URL to the source for the `PosterImage`. + * @return {string} + * The DOM `className` for this object. */ - 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; - } + DescriptionsButton.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-descriptions-button ' + _TextTrackButton.prototype.buildCSSClass.call(this); }; - /** - * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See - * {@link ClickableComponent#handleClick} for instances where this will be triggered. - * - * @listens tap - * @listens click - * @listens keydown - * - * @param {EventTarget~Event} event - + The `click`, `tap` or `keydown` event that caused this function to be called. - */ - - - PosterImage.prototype.handleClick = function handleClick(event) { - // We don't want a click to trigger playback when controls are disabled - if (!this.player_.controls()) { - return; - } - - if (this.player_.paused()) { - this.player_.play(); - } else { - this.player_.pause(); - } + DescriptionsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { + return 'vjs-descriptions-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); }; - return PosterImage; -}(_clickableComponent2['default']); - -_component2['default'].registerComponent('PosterImage', PosterImage); -exports['default'] = PosterImage; - -},{"3":3,"5":5,"81":81,"85":85,"88":88}],59:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; -exports.hasLoaded = exports.autoSetupTimeout = exports.autoSetup = undefined; - -var _dom = _dereq_(85); - -var Dom = _interopRequireWildcard(_dom); - -var _events = _dereq_(86); - -var Events = _interopRequireWildcard(_events); - -var _document = _dereq_(99); - -var _document2 = _interopRequireDefault(_document); - -var _window = _dereq_(100); + return DescriptionsButton; +}(TextTrackButton); -var _window2 = _interopRequireDefault(_window); +/** + * `kind` of TextTrack to look for to associate it with this menu. + * + * @type {string} + * @private + */ -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; } } +DescriptionsButton.prototype.kind_ = 'descriptions'; /** - * @file setup.js - Functions for setting up a player without - * user interaction based on the data-setup `attribute` of the video tag. + * The text that should display over the `DescriptionsButton`s controls. Added for localization. * - * @module setup + * @type {string} + * @private */ -var _windowLoaded = false; -var videojs = void 0; +DescriptionsButton.prototype.controlText_ = 'Descriptions'; + +Component.registerComponent('DescriptionsButton', DescriptionsButton); /** - * Set up any tags that have a data-setup `attribute` when the player is started. + * @file subtitles-button.js */ -var autoSetup = function autoSetup() { - - // Protect against breakage in non-browser environments. - if (!Dom.isReal()) { - return; - } - - // 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 = _document2['default'].getElementsByTagName('video'); - var audios = _document2['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 _i2 = 0, _e2 = mediaEls.length; _i2 < _e2; _i2++) { - var mediaEl = mediaEls[_i2]; - - // 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. - 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); - } -}; - /** - * Wait until the page is loaded before running autoSetup. This will be called in - * autoSetup if `hasLoaded` returns false. - * - * @param {number} wait - * How long to wait in ms + * The button component for toggling and selecting subtitles * - * @param {module:videojs} [vjs] - * The videojs library function + * @extends TextTrackButton */ -function autoSetupTimeout(wait, vjs) { - if (vjs) { - videojs = vjs; - } - _window2['default'].setTimeout(autoSetup, wait); -} +var SubtitlesButton = function (_TextTrackButton) { + inherits(SubtitlesButton, _TextTrackButton); -if (Dom.isReal() && _document2['default'].readyState === 'complete') { - _windowLoaded = true; -} else { /** - * Listen for the load event on window, and set _windowLoaded to true. + * Creates an instance of this class. * - * @listens load + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + * + * @param {Component~ReadyCallback} [ready] + * The function to call when this component is ready. */ - Events.one(_window2['default'], 'load', function () { - _windowLoaded = true; - }); -} - -/** - * check if the document has been loaded - */ -var hasLoaded = function hasLoaded() { - return _windowLoaded; -}; - -exports.autoSetup = autoSetup; -exports.autoSetupTimeout = autoSetupTimeout; -exports.hasLoaded = hasLoaded; - -},{"100":100,"85":85,"86":86,"99":99}],60:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _component = _dereq_(5); + function SubtitlesButton(player, options, ready) { + classCallCheck(this, SubtitlesButton); + return possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready)); + } -var _component2 = _interopRequireDefault(_component); + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ -var _dom = _dereq_(85); -var Dom = _interopRequireWildcard(_dom); + SubtitlesButton.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-subtitles-button ' + _TextTrackButton.prototype.buildCSSClass.call(this); + }; -var _obj = _dereq_(93); + SubtitlesButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { + return 'vjs-subtitles-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); + }; -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 SubtitlesButton; +}(TextTrackButton); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } +/** + * `kind` of TextTrack to look for to associate it with this menu. + * + * @type {string} + * @private + */ -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } +SubtitlesButton.prototype.kind_ = 'subtitles'; -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; } /** - * @file slider.js - */ +/** + * The text that should display over the `SubtitlesButton`s controls. Added for localization. + * + * @type {string} + * @private + */ +SubtitlesButton.prototype.controlText_ = 'Subtitles'; +Component.registerComponent('SubtitlesButton', SubtitlesButton); /** - * The base functionality for a slider. Can be vertical or horizontal. - * For instance the volume bar or the seek bar on a video is a slider. + * @file caption-settings-menu-item.js + */ +/** + * The menu item for caption track settings menu * - * @extends Component + * @extends TextTrackMenuItem */ -var Slider = function (_Component) { - _inherits(Slider, _Component); + +var CaptionSettingsMenuItem = function (_TextTrackMenuItem) { + inherits(CaptionSettingsMenuItem, _TextTrackMenuItem); /** - * Create an instance of this class + * Creates an instance of this class. * * @param {Player} player * The `Player` that this class should be attached to. @@ -14606,12805 +17424,8638 @@ var Slider = function (_Component) { * @param {Object} [options] * The key/value store of player options. */ - function Slider(player, options) { - _classCallCheck(this, Slider); + function CaptionSettingsMenuItem(player, options) { + classCallCheck(this, CaptionSettingsMenuItem); - // Set property names to bar to match with the child Slider class is looking for - var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); + options.track = { + player: player, + kind: options.kind, + label: options.kind + ' settings', + selectable: false, + 'default': false, + mode: 'disabled' + }; - _this.bar = _this.getChild(_this.options_.barName); + // CaptionSettingsMenuItem has no concept of 'selected' + options.selectable = false; - // 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); + options.name = 'CaptionSettingsMenuItem'; - _this.on(player, 'controlsvisible', _this.update); + var _this = possibleConstructorReturn(this, _TextTrackMenuItem.call(this, player, options)); - if (_this.playerEvent) { - _this.on(player, _this.playerEvent, _this.update); - } + _this.addClass('vjs-texttrack-settings'); + _this.controlText(', opens ' + options.kind + ' settings dialog'); return _this; } /** - * Create the `Button`s DOM element. - * - * @param {string} type - * Type of element to create. - * - * @param {Object} [props={}] - * List of properties in Object form. + * This gets called when an `CaptionSettingsMenuItem` is "clicked". See + * {@link ClickableComponent} for more detailed information on what a click can be. * - * @param {Object} [attributes={}] - * list of attributes in Object form. + * @param {EventTarget~Event} [event] + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. * - * @return {Element} - * The element that gets created. + * @listens tap + * @listens click */ - 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] : {}; + CaptionSettingsMenuItem.prototype.handleClick = function handleClick(event) { + this.player().getChild('textTrackSettings').open(); + }; - // Add the slider element class to all sub classes - props.className = props.className + ' vjs-slider'; - props = (0, _obj.assign)({ - tabIndex: 0 - }, props); + return CaptionSettingsMenuItem; +}(TextTrackMenuItem); - attributes = (0, _obj.assign)({ - 'role': 'slider', - 'aria-valuenow': 0, - 'aria-valuemin': 0, - 'aria-valuemax': 100, - 'tabIndex': 0 - }, attributes); +Component.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem); - return _Component.prototype.createEl.call(this, type, props, attributes); - }; +/** + * @file captions-button.js + */ +/** + * The button component for toggling and selecting captions + * + * @extends TextTrackButton + */ + +var CaptionsButton = function (_TextTrackButton) { + inherits(CaptionsButton, _TextTrackButton); /** - * Handle `mousedown` or `touchstart` events on the `Slider`. + * Creates an instance of this class. * - * @param {EventTarget~Event} event - * `mousedown` or `touchstart` event that triggered this function + * @param {Player} player + * The `Player` that this class should be attached to. * - * @listens mousedown - * @listens touchstart - * @fires Slider#slideractive + * @param {Object} [options] + * The key/value store of player options. + * + * @param {Component~ReadyCallback} [ready] + * The function to call when this component is ready. */ + function CaptionsButton(player, options, ready) { + classCallCheck(this, CaptionsButton); + return possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready)); + } + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ - Slider.prototype.handleMouseDown = function handleMouseDown(event) { - var doc = this.bar.el_.ownerDocument; - - event.preventDefault(); - Dom.blockTextSelection(); - - this.addClass('vjs-sliding'); - /** - * Triggered when the slider is in an active state - * - * @event Slider#slideractive - * @type {EventTarget~Event} - */ - 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); + CaptionsButton.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-captions-button ' + _TextTrackButton.prototype.buildCSSClass.call(this); + }; - this.handleMouseMove(event); + CaptionsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { + return 'vjs-captions-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); }; /** - * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`. - * The `mousemove` and `touchmove` events will only only trigger this function during - * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and - * {@link Slider#handleMouseUp}. - * - * @param {EventTarget~Event} event - * `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered - * this function + * Create caption menu items * - * @listens mousemove - * @listens touchmove + * @return {CaptionSettingsMenuItem[]} + * The array of current menu items. */ - Slider.prototype.handleMouseMove = function handleMouseMove(event) {}; + CaptionsButton.prototype.createItems = function createItems() { + var items = []; - /** - * Handle `mouseup` or `touchend` events on the `Slider`. - * - * @param {EventTarget~Event} event - * `mouseup` or `touchend` event that triggered this function. - * - * @listens touchend - * @listens mouseup - * @fires Slider#sliderinactive - */ + if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks)) { + items.push(new CaptionSettingsMenuItem(this.player_, { kind: this.kind_ })); + this.hideThreshold_ += 1; + } - Slider.prototype.handleMouseUp = function handleMouseUp() { - var doc = this.bar.el_.ownerDocument; + return _TextTrackButton.prototype.createItems.call(this, items); + }; - Dom.unblockTextSelection(); + return CaptionsButton; +}(TextTrackButton); - this.removeClass('vjs-sliding'); - /** - * Triggered when the slider is no longer in an active state. - * - * @event Slider#sliderinactive - * @type {EventTarget~Event} - */ - this.trigger('sliderinactive'); +/** + * `kind` of TextTrack to look for to associate it with this menu. + * + * @type {string} + * @private + */ - 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(); - }; +CaptionsButton.prototype.kind_ = 'captions'; - /** - * Update the progress bar of the `Slider`. - * - * @returns {number} - * The percentage of progress the progress bar represents as a - * number from 0 to 1. - */ +/** + * The text that should display over the `CaptionsButton`s controls. Added for localization. + * + * @type {string} + * @private + */ +CaptionsButton.prototype.controlText_ = 'Captions'; +Component.registerComponent('CaptionsButton', CaptionsButton); - Slider.prototype.update = function update() { +/** + * @file subs-caps-menu-item.js + */ +/** + * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles + * in the SubsCapsMenu. + * + * @extends TextTrackMenuItem + */ - // 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 SubsCapsMenuItem = function (_TextTrackMenuItem) { + inherits(SubsCapsMenuItem, _TextTrackMenuItem); - // 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; + function SubsCapsMenuItem() { + classCallCheck(this, SubsCapsMenuItem); + return possibleConstructorReturn(this, _TextTrackMenuItem.apply(this, arguments)); + } - // If there's no bar... - if (!bar) { - return; - } + SubsCapsMenuItem.prototype.createEl = function createEl(type, props, attrs) { + var innerHTML = '' + this.localize(this.options_.label); - // Protect against no duration and other division issues - if (typeof progress !== 'number' || progress !== progress || progress < 0 || progress === Infinity) { - progress = 0; + if (this.options_.track.kind === 'captions') { + innerHTML += '\n \n ' + this.localize('Captions') + '\n '; } - // Convert to a percentage for setting - var percentage = (progress * 100).toFixed(2) + '%'; - var style = bar.el().style; + innerHTML += ''; - // Set the new bar width or height - if (this.vertical()) { - style.height = percentage; - } else { - style.width = percentage; - } + var el = _TextTrackMenuItem.prototype.createEl.call(this, type, assign({ + innerHTML: innerHTML + }, props), attrs); - return progress; + return el; }; - /** - * Calculate distance for slider - * - * @param {EventTarget~Event} event - * The event that caused this function to run. - * - * @return {number} - * The current position of the Slider. - * - postition.x for vertical `Slider`s - * - postition.y for horizontal `Slider`s - */ + return SubsCapsMenuItem; +}(TextTrackMenuItem); +Component.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem); - Slider.prototype.calculateDistance = function calculateDistance(event) { - var position = Dom.getPointerPosition(this.el_, event); +/** + * @file sub-caps-button.js + */ +/** + * The button component for toggling and selecting captions and/or subtitles + * + * @extends TextTrackButton + */ - if (this.vertical()) { - return position.y; +var SubsCapsButton = function (_TextTrackButton) { + inherits(SubsCapsButton, _TextTrackButton); + + function SubsCapsButton(player) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + classCallCheck(this, SubsCapsButton); + + // Although North America uses "captions" in most cases for + // "captions and subtitles" other locales use "subtitles" + var _this = possibleConstructorReturn(this, _TextTrackButton.call(this, player, options)); + + _this.label_ = 'subtitles'; + if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(_this.player_.language_) > -1) { + _this.label_ = 'captions'; } - return position.x; - }; + _this.menuButton_.controlText(toTitleCase(_this.label_)); + return _this; + } /** - * Handle a `focus` event on this `Slider`. + * Builds the default DOM `className`. * - * @param {EventTarget~Event} event - * The `focus` event that caused this function to run. - * - * @listens focus + * @return {string} + * The DOM `className` for this object. */ - Slider.prototype.handleFocus = function handleFocus() { - this.on(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress); + SubsCapsButton.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-subs-caps-button ' + _TextTrackButton.prototype.buildCSSClass.call(this); + }; + + SubsCapsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { + return 'vjs-subs-caps-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); }; /** - * Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down - * arrow keys. This function will only be called when the slider has focus. See - * {@link Slider#handleFocus} and {@link Slider#handleBlur}. - * - * @param {EventTarget~Event} event - * the `keydown` event that caused this function to run. + * Create caption/subtitles menu items * - * @listens keydown + * @return {CaptionSettingsMenuItem[]} + * The array of current menu items. */ - Slider.prototype.handleKeyPress = function handleKeyPress(event) { - // Left and Down Arrows - if (event.which === 37 || event.which === 40) { - event.preventDefault(); - this.stepBack(); + SubsCapsButton.prototype.createItems = function createItems() { + var items = []; - // Up and Right Arrows - } else if (event.which === 38 || event.which === 39) { - event.preventDefault(); - this.stepForward(); + if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks)) { + items.push(new CaptionSettingsMenuItem(this.player_, { kind: this.label_ })); + + this.hideThreshold_ += 1; } + + items = _TextTrackButton.prototype.createItems.call(this, items, SubsCapsMenuItem); + return items; }; - /** - * Handle a `blur` event on this `Slider`. - * - * @param {EventTarget~Event} event - * The `blur` event that caused this function to run. - * - * @listens blur - */ + return SubsCapsButton; +}(TextTrackButton); - Slider.prototype.handleBlur = function handleBlur() { - this.off(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress); - }; +/** + * `kind`s of TextTrack to look for to associate it with this menu. + * + * @type {array} + * @private + */ - /** - * Listener for click events on slider, used to prevent clicks - * from bubbling up to parent elements like button menus. - * - * @param {Object} event - * Event that caused this object to run - */ +SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles']; + +/** + * The text that should display over the `SubsCapsButton`s controls. + * + * + * @type {string} + * @private + */ +SubsCapsButton.prototype.controlText_ = 'Subtitles'; + +Component.registerComponent('SubsCapsButton', SubsCapsButton); - Slider.prototype.handleClick = function handleClick(event) { - event.stopImmediatePropagation(); - event.preventDefault(); - }; +/** + * @file audio-track-menu-item.js + */ +/** + * An {@link AudioTrack} {@link MenuItem} + * + * @extends MenuItem + */ + +var AudioTrackMenuItem = function (_MenuItem) { + inherits(AudioTrackMenuItem, _MenuItem); /** - * Get/set if slider is horizontal for vertical + * Creates an instance of this class. * - * @param {boolean} [bool] - * - true if slider is vertical, - * - false is horizontal + * @param {Player} player + * The `Player` that this class should be attached to. * - * @return {boolean} - * - true if slider is vertical, and getting - * - false if the slider is horizontal, and getting + * @param {Object} [options] + * The key/value store of player options. */ + function AudioTrackMenuItem(player, options) { + classCallCheck(this, AudioTrackMenuItem); + var track = options.track; + var tracks = player.audioTracks(); - Slider.prototype.vertical = function vertical(bool) { - if (bool === undefined) { - return this.vertical_ || false; - } + // Modify options for parent MenuItem class's init. + options.label = track.label || track.language || 'Unknown'; + options.selected = track.enabled; - this.vertical_ = !!bool; + var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options)); - if (this.vertical_) { - this.addClass('vjs-slider-vertical'); - } else { - this.addClass('vjs-slider-horizontal'); - } - }; + _this.track = track; - return Slider; -}(_component2['default']); + var changeHandler = function changeHandler() { + for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + _this.handleTracksChange.apply(_this, args); + }; -_component2['default'].registerComponent('Slider', Slider); -exports['default'] = Slider; + tracks.addEventListener('change', changeHandler); + _this.on('dispose', function () { + tracks.removeEventListener('change', changeHandler); + }); + return _this; + } -},{"5":5,"85":85,"93":93}],61:[function(_dereq_,module,exports){ -'use strict'; + /** + * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent} + * for more detailed information on what a click can be. + * + * @param {EventTarget~Event} [event] + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click + */ -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.']); + AudioTrackMenuItem.prototype.handleClick = function handleClick(event) { + var tracks = this.player_.audioTracks(); -var _tech = _dereq_(64); + _MenuItem.prototype.handleClick.call(this, event); -var _tech2 = _interopRequireDefault(_tech); + for (var i = 0; i < tracks.length; i++) { + var track = tracks[i]; -var _dom = _dereq_(85); + track.enabled = track === this.track; + } + }; -var Dom = _interopRequireWildcard(_dom); + /** + * Handle any {@link AudioTrack} change. + * + * @param {EventTarget~Event} [event] + * The {@link AudioTrackList#change} event that caused this to run. + * + * @listens AudioTrackList#change + */ -var _url = _dereq_(97); -var Url = _interopRequireWildcard(_url); + AudioTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) { + this.selected(this.track.enabled); + }; -var _log = _dereq_(91); + return AudioTrackMenuItem; +}(MenuItem); -var _log2 = _interopRequireDefault(_log); +Component.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem); -var _tsml = _dereq_(103); +/** + * @file audio-track-button.js + */ +/** + * The base class for buttons that toggle specific {@link AudioTrack} types. + * + * @extends TrackButton + */ -var _tsml2 = _interopRequireDefault(_tsml); +var AudioTrackButton = function (_TrackButton) { + inherits(AudioTrackButton, _TrackButton); -var _browser = _dereq_(81); + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options={}] + * The key/value store of player options. + */ + function AudioTrackButton(player) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + classCallCheck(this, AudioTrackButton); -var browser = _interopRequireWildcard(_browser); + options.tracks = player.audioTracks(); -var _document = _dereq_(99); + return possibleConstructorReturn(this, _TrackButton.call(this, player, options)); + } -var _document2 = _interopRequireDefault(_document); + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ -var _window = _dereq_(100); -var _window2 = _interopRequireDefault(_window); + AudioTrackButton.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-audio-button ' + _TrackButton.prototype.buildCSSClass.call(this); + }; -var _obj = _dereq_(93); + AudioTrackButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { + return 'vjs-audio-button ' + _TrackButton.prototype.buildWrapperCSSClass.call(this); + }; -var _mergeOptions = _dereq_(92); + /** + * Create a menu item for each audio track + * + * @param {AudioTrackMenuItem[]} [items=[]] + * An array of existing menu items to use. + * + * @return {AudioTrackMenuItem[]} + * An array of menu items + */ -var _mergeOptions2 = _interopRequireDefault(_mergeOptions); -var _toTitleCase = _dereq_(96); + AudioTrackButton.prototype.createItems = function createItems() { + var items = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; -var _toTitleCase2 = _interopRequireDefault(_toTitleCase); + // if there's only one audio track, there no point in showing it + this.hideThreshold_ = 1; -var _trackTypes = _dereq_(77); + var tracks = this.player_.audioTracks(); -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; } } + for (var i = 0; i < tracks.length; i++) { + var track = tracks[i]; -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + items.push(new AudioTrackMenuItem(this.player_, { + track: track, + // MenuItem is selectable + selectable: true + })); + } -function _taggedTemplateLiteralLoose(strings, raw) { strings.raw = raw; return strings; } + return items; + }; -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + return AudioTrackButton; +}(TrackButton); -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } +/** + * The text that should display over the `AudioTrackButton`s controls. Added for localization. + * + * @type {string} + * @private + */ -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; } /** - * @file html5.js - */ +AudioTrackButton.prototype.controlText_ = 'Audio Track'; +Component.registerComponent('AudioTrackButton', AudioTrackButton); /** - * HTML5 Media Controller - Wrapper for HTML5 Media API + * @file playback-rate-menu-item.js + */ +/** + * The specific menu item type for selecting a playback rate. * - * @mixes Tech~SouceHandlerAdditions - * @extends Tech + * @extends MenuItem */ -var Html5 = function (_Tech) { - _inherits(Html5, _Tech); + +var PlaybackRateMenuItem = function (_MenuItem) { + inherits(PlaybackRateMenuItem, _MenuItem); /** - * Create an instance of this Tech. + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. * * @param {Object} [options] * The key/value store of player options. - * - * @param {Component~ReadyCallback} ready - * Callback function to call when the `HTML5` Tech is ready. */ - function Html5(options, ready) { - _classCallCheck(this, Html5); + function PlaybackRateMenuItem(player, options) { + classCallCheck(this, PlaybackRateMenuItem); - var _this = _possibleConstructorReturn(this, _Tech.call(this, options, ready)); + var label = options.rate; + var rate = parseFloat(label, 10); - var source = options.source; - var crossoriginTracks = false; + // Modify options for parent MenuItem class's init. + options.label = label; + options.selected = rate === 1; + options.selectable = true; - // 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); - _this.textTracks().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]); - } - } - - _this.proxyNativeTracks_(); - if (_this.featuresNativeTextTracks && crossoriginTracks) { - _log2['default'].warn((0, _tsml2['default'])(_templateObject)); - } + var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options)); - // 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 || browser.IS_IPHONE || browser.IS_NATIVE_ANDROID) && options.nativeControlsForTouch === true) { - _this.setControls(true); - } - - // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen` - // into a `fullscreenchange` event - _this.proxyWebkitFullscreen_(); + _this.label = label; + _this.rate = rate; - _this.triggerReady(); + _this.on(player, 'ratechange', _this.update); return _this; } /** - * Dispose of `HTML5` media element and remove all tracks. + * This gets called when an `PlaybackRateMenuItem` is "clicked". See + * {@link ClickableComponent} for more detailed information on what a click can be. + * + * @param {EventTarget~Event} [event] + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click */ - Html5.prototype.dispose = function dispose() { - Html5.disposeMediaElement(this.el_); - // tech will handle clearing of the emulated track list - _Tech.prototype.dispose.call(this); + PlaybackRateMenuItem.prototype.handleClick = function handleClick(event) { + _MenuItem.prototype.handleClick.call(this); + this.player().playbackRate(this.rate); }; /** - * Proxy all native track list events to our track lists if the browser we are playing - * in supports that type of track list. + * Update the PlaybackRateMenuItem when the playbackrate changes. * - * @private + * @param {EventTarget~Event} [event] + * The `ratechange` event that caused this function to run. + * + * @listens Player#ratechange */ - Html5.prototype.proxyNativeTracks_ = function proxyNativeTracks_() { - var _this2 = this; + PlaybackRateMenuItem.prototype.update = function update(event) { + this.selected(this.player().playbackRate() === this.rate); + }; - _trackTypes.NORMAL.names.forEach(function (name) { - var props = _trackTypes.NORMAL[name]; - var elTracks = _this2.el()[props.getterName]; - var techTracks = _this2[props.getterName](); + return PlaybackRateMenuItem; +}(MenuItem); - if (!_this2['featuresNative' + props.capitalName + 'Tracks'] || !elTracks || !elTracks.addEventListener) { - return; - } - var listeners = { - change: function change(e) { - techTracks.trigger({ - type: 'change', - target: techTracks, - currentTarget: techTracks, - srcElement: techTracks - }); - }, - addtrack: function addtrack(e) { - techTracks.addTrack(e.track); - }, - removetrack: function removetrack(e) { - techTracks.removeTrack(e.track); - } - }; - var removeOldTracks = function removeOldTracks() { - var removeTracks = []; +/** + * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization. + * + * @type {string} + * @private + */ - for (var i = 0; i < techTracks.length; i++) { - var found = false; - for (var j = 0; j < elTracks.length; j++) { - if (elTracks[j] === techTracks[i]) { - found = true; - break; - } - } +PlaybackRateMenuItem.prototype.contentElType = 'button'; - if (!found) { - removeTracks.push(techTracks[i]); - } - } +Component.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem); - while (removeTracks.length) { - techTracks.removeTrack(removeTracks.shift()); - } - }; +/** + * @file playback-rate-menu-button.js + */ +/** + * The component for controlling the playback rate. + * + * @extends MenuButton + */ - Object.keys(listeners).forEach(function (eventName) { - var listener = listeners[eventName]; +var PlaybackRateMenuButton = function (_MenuButton) { + inherits(PlaybackRateMenuButton, _MenuButton); - elTracks.addEventListener(eventName, listener); - _this2.on('dispose', function (e) { - return elTracks.removeEventListener(eventName, listener); - }); - }); + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ + function PlaybackRateMenuButton(player, options) { + classCallCheck(this, PlaybackRateMenuButton); - // Remove (native) tracks that are not used anymore - _this2.on('loadstart', removeOldTracks); - _this2.on('dispose', function (e) { - return _this2.off('loadstart', removeOldTracks); - }); - }); - }; + var _this = possibleConstructorReturn(this, _MenuButton.call(this, player, options)); + + _this.updateVisibility(); + _this.updateLabel(); + + _this.on(player, 'loadstart', _this.updateVisibility); + _this.on(player, 'ratechange', _this.updateLabel); + return _this; + } /** - * Create the `Html5` Tech's DOM element. + * Create the `Component`'s DOM element * * @return {Element} - * The element that gets created. + * The element that was created. */ - Html5.prototype.createEl = function createEl() { - var el = this.options_.tag; + PlaybackRateMenuButton.prototype.createEl = function createEl$$1() { + var el = _MenuButton.prototype.createEl.call(this); - // 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 we ingested the player div, we do not need to move the media element. - if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) { + this.labelEl_ = createEl('div', { + className: 'vjs-playback-rate-value', + innerHTML: '1x' + }); - // If the original tag is still there, clone and remove it. - if (el) { - var clone = el.cloneNode(true); + el.appendChild(this.labelEl_); - if (el.parentNode) { - el.parentNode.insertBefore(clone, el); - } - Html5.disposeMediaElement(el); - el = clone; - } else { - el = _document2['default'].createElement('video'); + return el; + }; - // determine if native controls should be used - var tagAttributes = this.options_.tag && Dom.getAttributes(this.options_.tag); - var attributes = (0, _mergeOptions2['default'])({}, tagAttributes); + PlaybackRateMenuButton.prototype.dispose = function dispose() { + this.labelEl_ = null; - if (!browser.TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) { - delete attributes.controls; - } + _MenuButton.prototype.dispose.call(this); + }; - Dom.setAttributes(el, (0, _obj.assign)(attributes, { - id: this.options_.techId, - 'class': 'vjs-tech' - })); - } + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ - el.playerId = this.options_.playerId; - } - // Update specific tag settings, in case they were overridden - var settingsAttrs = ['autoplay', 'preload', 'loop', 'muted', 'playsinline']; + PlaybackRateMenuButton.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-playback-rate ' + _MenuButton.prototype.buildCSSClass.call(this); + }; + + PlaybackRateMenuButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { + return 'vjs-playback-rate ' + _MenuButton.prototype.buildWrapperCSSClass.call(this); + }; + + /** + * Create the playback rate menu + * + * @return {Menu} + * Menu object populated with {@link PlaybackRateMenuItem}s + */ - 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]; + PlaybackRateMenuButton.prototype.createMenu = function createMenu() { + var menu = new Menu(this.player()); + var rates = this.playbackRates(); + + if (rates) { + for (var i = rates.length - 1; i >= 0; i--) { + menu.addChild(new PlaybackRateMenuItem(this.player(), { rate: rates[i] + 'x' })); } - Dom.setAttributes(el, overwriteAttrs); } - return el; + return menu; }; /** - * This will be triggered if the loadstart event has already fired, before videojs was - * ready. Two known examples of when this can happen are: - * 1. If we're loading the playback object after it has started loading - * 2. The media is already playing the (often with autoplay on) then - * - * This function will fire another loadstart so that videojs can catchup. - * - * @fires Tech#loadstart - * - * @return {undefined} - * returns nothing. + * Updates ARIA accessibility attributes */ - Html5.prototype.handleLateInit_ = function handleLateInit_(el) { - 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) { - // 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; - }; - - this.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'); - } - }; - - this.on('loadedmetadata', triggerLoadstart); - - this.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; - } + PlaybackRateMenuButton.prototype.updateARIAAttributes = function updateARIAAttributes() { + // Current playback rate + this.el().setAttribute('aria-valuenow', this.player().playbackRate()); + }; - // 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']; + /** + * This gets called when an `PlaybackRateMenuButton` is "clicked". See + * {@link ClickableComponent} for more detailed information on what a click can be. + * + * @param {EventTarget~Event} [event] + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click + */ - // 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'); - } + PlaybackRateMenuButton.prototype.handleClick = function handleClick(event) { + // select next rate option + var currentRate = this.player().playbackRate(); + var rates = this.playbackRates(); - // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater - if (el.readyState >= 3) { - eventsToTrigger.push('canplay'); - } + // this will select first one if the last one currently selected + var newRate = rates[0]; - // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4) - if (el.readyState >= 4) { - eventsToTrigger.push('canplaythrough'); + for (var i = 0; i < rates.length; i++) { + if (rates[i] > currentRate) { + newRate = rates[i]; + break; + } } - - // We still need to give the player time to add event listeners - this.ready(function () { - eventsToTrigger.forEach(function (type) { - this.trigger(type); - }, this); - }); + this.player().playbackRate(newRate); }; /** - * Set current time for the `HTML5` tech. + * Get possible playback rates * - * @param {number} seconds - * Set the current time of the media to this. + * @return {Array} + * All possible playback rates */ - Html5.prototype.setCurrentTime = function setCurrentTime(seconds) { - try { - this.el_.currentTime = seconds; - } catch (e) { - (0, _log2['default'])(e, 'Video is not ready. (Video.js)'); - // this.warning(VideoJS.warnings.videoNotReady); - } + PlaybackRateMenuButton.prototype.playbackRates = function playbackRates() { + return this.options_.playbackRates || this.options_.playerOptions && this.options_.playerOptions.playbackRates; }; /** - * Get the current duration of the HTML5 media element. + * Get whether playback rates is supported by the tech + * and an array of playback rates exists * - * @return {number} - * The duration of the media or 0 if there is no duration. + * @return {boolean} + * Whether changing playback rate is supported */ - Html5.prototype.duration = function duration() { - var _this3 = this; - - // Android Chrome will report duration as Infinity for VOD HLS until after - // playback has started, which triggers the live display erroneously. - // Return NaN if playback has not started and trigger a durationupdate once - // the duration can be reliably known. - if (this.el_.duration === Infinity && browser.IS_ANDROID && browser.IS_CHROME) { - if (this.el_.currentTime === 0) { - // Wait for the first `timeupdate` with currentTime > 0 - there may be - // several with 0 - var checkProgress = function checkProgress() { - if (_this3.el_.currentTime > 0) { - // Trigger durationchange for genuinely live video - if (_this3.el_.duration === Infinity) { - _this3.trigger('durationchange'); - } - _this3.off('timeupdate', checkProgress); - } - }; - - this.on('timeupdate', checkProgress); - return NaN; - } - } - return this.el_.duration || NaN; + PlaybackRateMenuButton.prototype.playbackRateSupported = function playbackRateSupported() { + return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0; }; /** - * Get the current width of the HTML5 media element. + * Hide playback rate controls when they're no playback rate options to select * - * @return {number} - * The width of the HTML5 media element. + * @param {EventTarget~Event} [event] + * The event that caused this function to run. + * + * @listens Player#loadstart */ - Html5.prototype.width = function width() { - return this.el_.offsetWidth; + PlaybackRateMenuButton.prototype.updateVisibility = function updateVisibility(event) { + if (this.playbackRateSupported()) { + this.removeClass('vjs-hidden'); + } else { + this.addClass('vjs-hidden'); + } }; /** - * Get the current height of the HTML5 media element. + * Update button label when rate changed * - * @return {number} - * The heigth of the HTML5 media element. + * @param {EventTarget~Event} [event] + * The event that caused this function to run. + * + * @listens Player#ratechange */ - Html5.prototype.height = function height() { - return this.el_.offsetHeight; + PlaybackRateMenuButton.prototype.updateLabel = function updateLabel(event) { + if (this.playbackRateSupported()) { + this.labelEl_.innerHTML = this.player().playbackRate() + 'x'; + } }; - /** - * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into - * `fullscreenchange` event. - * - * @private - * @fires fullscreenchange - * @listens webkitendfullscreen - * @listens webkitbeginfullscreen - * @listens webkitbeginfullscreen - */ + return PlaybackRateMenuButton; +}(MenuButton); +/** + * The text that should display over the `FullscreenToggle`s controls. Added for localization. + * + * @type {string} + * @private + */ - Html5.prototype.proxyWebkitFullscreen_ = function proxyWebkitFullscreen_() { - var _this4 = this; - if (!('webkitDisplayingFullscreen' in this.el_)) { - return; - } +PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate'; - var endFn = function endFn() { - this.trigger('fullscreenchange', { isFullscreen: false }); - }; +Component.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton); - var beginFn = function beginFn() { - this.one('webkitendfullscreen', endFn); +/** + * @file spacer.js + */ +/** + * Just an empty spacer element that can be used as an append point for plugins, etc. + * Also can be used to create space between elements when necessary. + * + * @extends Component + */ - this.trigger('fullscreenchange', { isFullscreen: true }); - }; +var Spacer = function (_Component) { + inherits(Spacer, _Component); - this.on('webkitbeginfullscreen', beginFn); - this.on('dispose', function () { - _this4.off('webkitbeginfullscreen', beginFn); - _this4.off('webkitendfullscreen', endFn); - }); - }; + function Spacer() { + classCallCheck(this, Spacer); + return possibleConstructorReturn(this, _Component.apply(this, arguments)); + } /** - * Check if fullscreen is supported on the current playback device. + * Builds the default DOM `className`. * - * @return {boolean} - * - True if fullscreen is supported. - * - False if fullscreen is not supported. + * @return {string} + * The DOM `className` for this object. */ - - - Html5.prototype.supportsFullScreen = function supportsFullScreen() { - if (typeof this.el_.webkitEnterFullScreen === 'function') { - var userAgent = _window2['default'].navigator && _window2['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; + Spacer.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-spacer ' + _Component.prototype.buildCSSClass.call(this); }; /** - * Request that the `HTML5` Tech enter fullscreen. + * Create the `Component`'s DOM element + * + * @return {Element} + * The element that was created. */ - Html5.prototype.enterFullScreen = function enterFullScreen() { - var video = this.el_; - - 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(); - } + Spacer.prototype.createEl = function createEl() { + return _Component.prototype.createEl.call(this, 'div', { + className: this.buildCSSClass() + }); }; - /** - * Request that the `HTML5` Tech exit fullscreen. - */ - + return Spacer; +}(Component); - Html5.prototype.exitFullScreen = function exitFullScreen() { - this.el_.webkitExitFullScreen(); - }; +Component.registerComponent('Spacer', Spacer); - /** - * A getter/setter for the `Html5` Tech's source object. - * > Note: Please use {@link Html5#setSource} - * - * @param {Tech~SourceObject} [src] - * The source object you want to set on the `HTML5` techs element. - * - * @return {Tech~SourceObject|undefined} - * - The current source object when a source is not passed in. - * - undefined when setting - * - * @deprecated Since version 5. - */ - - - Html5.prototype.src = function src(_src) { - if (_src === undefined) { - return this.el_.src; - } - - // Setting src through `src` instead of `setSrc` will be deprecated - this.setSrc(_src); - }; - - /** - * Reset the tech by removing all sources and then calling - * {@link Html5.resetMediaElement}. - */ +/** + * @file custom-control-spacer.js + */ +/** + * Spacer specifically meant to be used as an insertion point for new plugins, etc. + * + * @extends Spacer + */ +var CustomControlSpacer = function (_Spacer) { + inherits(CustomControlSpacer, _Spacer); - Html5.prototype.reset = function reset() { - Html5.resetMediaElement(this.el_); - }; + function CustomControlSpacer() { + classCallCheck(this, CustomControlSpacer); + return possibleConstructorReturn(this, _Spacer.apply(this, arguments)); + } /** - * Get the current source on the `HTML5` Tech. Falls back to returning the source from - * the HTML5 media element. + * Builds the default DOM `className`. * - * @return {Tech~SourceObject} - * The current source object from the HTML5 tech. With a fallback to the - * elements source. + * @return {string} + * The DOM `className` for this object. */ - - - Html5.prototype.currentSrc = function currentSrc() { - if (this.currentSource_) { - return this.currentSource_.src; - } - return this.el_.currentSrc; + CustomControlSpacer.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-custom-control-spacer ' + _Spacer.prototype.buildCSSClass.call(this); }; /** - * Set controls attribute for the HTML5 media Element. + * Create the `Component`'s DOM element * - * @param {string} val - * Value to set the controls attribute to + * @return {Element} + * The element that was created. */ - Html5.prototype.setControls = function setControls(val) { - this.el_.controls = !!val; + CustomControlSpacer.prototype.createEl = function createEl() { + var el = _Spacer.prototype.createEl.call(this, { + className: this.buildCSSClass() + }); + + // No-flex/table-cell mode requires there be some content + // in the cell to fill the remaining space of the table. + el.innerHTML = '\xA0'; + return el; }; - /** - * Create and returns a remote {@link TextTrack} object. - * - * @param {string} kind - * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) - * - * @param {string} [label] - * Label to identify the text track - * - * @param {string} [language] - * Two letter language abbreviation - * - * @return {TextTrack} - * The TextTrack that gets created. - */ + return CustomControlSpacer; +}(Spacer); +Component.registerComponent('CustomControlSpacer', CustomControlSpacer); - Html5.prototype.addTextTrack = function addTextTrack(kind, label, language) { - if (!this.featuresNativeTextTracks) { - return _Tech.prototype.addTextTrack.call(this, kind, label, language); - } +/** + * @file control-bar.js + */ +// Required children +/** + * Container of main controls. + * + * @extends Component + */ - return this.el_.addTextTrack(kind, label, language); - }; +var ControlBar = function (_Component) { + inherits(ControlBar, _Component); + + function ControlBar() { + classCallCheck(this, ControlBar); + return possibleConstructorReturn(this, _Component.apply(this, arguments)); + } /** - * Creates either native TextTrack or an emulated TextTrack depending - * on the value of `featuresNativeTextTracks` - * - * @param {Object} options - * The object should contain the options to intialize the TextTrack with. - * - * @param {string} [options.kind] - * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata). - * - * @param {string} [options.label]. - * Label to identify the text track - * - * @param {string} [options.language] - * Two letter language abbreviation. - * - * @param {boolean} [options.default] - * Default this track to on. - * - * @param {string} [options.id] - * The internal id to assign this track. - * - * @param {string} [options.src] - * A source url for the track. + * Create the `Component`'s DOM element * - * @return {HTMLTrackElement} - * The track element that gets created. + * @return {Element} + * The element that was created. */ + ControlBar.prototype.createEl = function createEl() { + return _Component.prototype.createEl.call(this, 'div', { + className: 'vjs-control-bar', + dir: 'ltr' + }, { + // The control bar is a group, but we don't aria-label it to avoid + // over-announcing by JAWS + role: 'group' + }); + }; + return ControlBar; +}(Component); - Html5.prototype.createRemoteTextTrack = function createRemoteTextTrack(options) { - if (!this.featuresNativeTextTracks) { - return _Tech.prototype.createRemoteTextTrack.call(this, options); - } - var htmlTrackElement = _document2['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; - } - - return htmlTrackElement; - }; +/** + * Default options for `ControlBar` + * + * @type {Object} + * @private + */ - /** - * Creates a remote text track object and returns an html track element. - * - * @param {Object} options The object should contain values for - * kind, language, label, and src (location of the WebVTT file) - * @param {Boolean} [manualCleanup=true] if set to false, the TextTrack will be - * automatically removed from the video element whenever the source changes - * @return {HTMLTrackElement} An Html Track Element. - * This can be an emulated {@link HTMLTrackElement} or a native one. - * @deprecated The default value of the "manualCleanup" parameter will default - * to "false" in upcoming versions of Video.js - */ +ControlBar.prototype.options_ = { + children: ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'fullscreenToggle'] +}; - Html5.prototype.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { - var htmlTrackElement = _Tech.prototype.addRemoteTextTrack.call(this, options, manualCleanup); +Component.registerComponent('ControlBar', ControlBar); - if (this.featuresNativeTextTracks) { - this.el().appendChild(htmlTrackElement); - } +/** + * @file error-display.js + */ +/** + * A display that indicates an error has occurred. This means that the video + * is unplayable. + * + * @extends ModalDialog + */ - return htmlTrackElement; - }; +var ErrorDisplay = function (_ModalDialog) { + inherits(ErrorDisplay, _ModalDialog); /** - * Remove remote `TextTrack` from `TextTrackList` object + * Creates an instance of this class. * - * @param {TextTrack} track - * `TextTrack` object to remove + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. */ + function ErrorDisplay(player, options) { + classCallCheck(this, ErrorDisplay); + var _this = possibleConstructorReturn(this, _ModalDialog.call(this, player, options)); - Html5.prototype.removeRemoteTextTrack = function removeRemoteTextTrack(track) { - _Tech.prototype.removeRemoteTextTrack.call(this, track); - - if (this.featuresNativeTextTracks) { - var tracks = this.$$('track'); - - var i = tracks.length; - - while (i--) { - if (track === tracks[i] || track === tracks[i].track) { - this.el().removeChild(tracks[i]); - } - } - } - }; + _this.on(player, 'error', _this.open); + return _this; + } /** - * Get the value of `playsinline` from the media element. `playsinline` indicates - * to the browser that non-fullscreen playback is preferred when fullscreen - * playback is the native default, such as in iOS Safari. + * Builds the default DOM `className`. * - * @method Html5#playsinline - * @return {boolean} - * - The value of `playsinline` from the media element. - * - True indicates that the media should play inline. - * - False indicates that the media should not play inline. + * @return {string} + * The DOM `className` for this object. * - * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} + * @deprecated Since version 5. */ - Html5.prototype.playsinline = function playsinline() { - return this.el_.hasAttribute('playsinline'); + ErrorDisplay.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-error-display ' + _ModalDialog.prototype.buildCSSClass.call(this); }; /** - * Set the value of `playsinline` from the media element. `playsinline` indicates - * to the browser that non-fullscreen playback is preferred when fullscreen - * playback is the native default, such as in iOS Safari. - * - * @method Html5#setPlaysinline - * @param {boolean} playsinline - * - True indicates that the media should play inline. - * - False indicates that the media should not play inline. + * Gets the localized error message based on the `Player`s error. * - * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} + * @return {string} + * The `Player`s error message localized or an empty string. */ - Html5.prototype.setPlaysinline = function setPlaysinline(value) { - if (value) { - this.el_.setAttribute('playsinline', 'playsinline'); - } else { - this.el_.removeAttribute('playsinline'); - } - }; + ErrorDisplay.prototype.content = function content() { + var error = this.player().error(); - /** - * Gets available media playback quality metrics as specified by the W3C's Media - * Playback Quality API. - * - * @see [Spec]{@link https://wicg.github.io/media-playback-quality} - * - * @return {Object} - * An object with supported media playback quality metrics - */ + return error ? this.localize(error.message) : ''; + }; + return ErrorDisplay; +}(ModalDialog); - Html5.prototype.getVideoPlaybackQuality = function getVideoPlaybackQuality() { - if (typeof this.el().getVideoPlaybackQuality === 'function') { - return this.el().getVideoPlaybackQuality(); - } +/** + * The default options for an `ErrorDisplay`. + * + * @private + */ - var videoPlaybackQuality = {}; - if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') { - videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount; - videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount; - } +ErrorDisplay.prototype.options_ = mergeOptions(ModalDialog.prototype.options_, { + pauseOnOpen: false, + fillAlways: true, + temporary: false, + uncloseable: true +}); - if (_window2['default'].performance && typeof _window2['default'].performance.now === 'function') { - videoPlaybackQuality.creationTime = _window2['default'].performance.now(); - } else if (_window2['default'].performance && _window2['default'].performance.timing && typeof _window2['default'].performance.timing.navigationStart === 'number') { - videoPlaybackQuality.creationTime = _window2['default'].Date.now() - _window2['default'].performance.timing.navigationStart; - } +Component.registerComponent('ErrorDisplay', ErrorDisplay); - return videoPlaybackQuality; - }; +/** + * @file text-track-settings.js + */ +var LOCAL_STORAGE_KEY = 'vjs-text-track-settings'; - return Html5; -}(_tech2['default']); +var COLOR_BLACK = ['#000', 'Black']; +var COLOR_BLUE = ['#00F', 'Blue']; +var COLOR_CYAN = ['#0FF', 'Cyan']; +var COLOR_GREEN = ['#0F0', 'Green']; +var COLOR_MAGENTA = ['#F0F', 'Magenta']; +var COLOR_RED = ['#F00', 'Red']; +var COLOR_WHITE = ['#FFF', 'White']; +var COLOR_YELLOW = ['#FF0', 'Yellow']; -/* HTML5 Support Testing ---------------------------------------------------- */ +var OPACITY_OPAQUE = ['1', 'Opaque']; +var OPACITY_SEMI = ['0.5', 'Semi-Transparent']; +var OPACITY_TRANS = ['0', 'Transparent']; -if (Dom.isReal()) { +// Configuration for the various element. +var selectConfigs = { + backgroundColor: { + selector: '.vjs-bg-color > select', + id: 'captions-background-color-%s', + label: 'Color', + options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN] + }, - /** - * Element for testing browser HTML5 media capabilities - * - * @type {Element} - * @constant - * @private - */ - Html5.TEST_VID = _document2['default'].createElement('video'); - var track = _document2['default'].createElement('track'); + backgroundOpacity: { + selector: '.vjs-bg-opacity > select', + id: 'captions-background-opacity-%s', + label: 'Transparency', + options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS] + }, - track.kind = 'captions'; - track.srclang = 'en'; - track.label = 'English'; - Html5.TEST_VID.appendChild(track); -} + color: { + selector: '.vjs-fg-color > select', + id: 'captions-foreground-color-%s', + label: 'Color', + options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN] + }, -/** - * Check if HTML5 media is supported by this browser/device. - * - * @return {boolean} - * - True if HTML5 media is supported. - * - False if HTML5 media is not supported. - */ -Html5.isSupported = function () { - // IE9 with no Media Player is a LIAR! (#984) - try { - Html5.TEST_VID.volume = 0.5; - } catch (e) { - return false; - } + edgeStyle: { + selector: '.vjs-edge-style > select', + id: '%s', + label: 'Text Edge Style', + options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Dropshadow']] + }, - return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType); -}; + fontFamily: { + selector: '.vjs-font-family > select', + id: 'captions-font-family-%s', + label: 'Font Family', + options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', 'Small Caps']] + }, -/** - * Check if the tech can support the given type - * - * @param {string} type - * The mimetype to check - * @return {string} 'probably', 'maybe', or '' (empty string) - */ -Html5.canPlayType = function (type) { - return Html5.TEST_VID.canPlayType(type); -}; + fontPercent: { + selector: '.vjs-font-percent > select', + id: 'captions-font-size-%s', + label: 'Font Size', + options: [['0.50', '50%'], ['0.75', '75%'], ['1.00', '100%'], ['1.25', '125%'], ['1.50', '150%'], ['1.75', '175%'], ['2.00', '200%'], ['3.00', '300%'], ['4.00', '400%']], + 'default': 2, + parser: function parser(v) { + return v === '1.00' ? null : Number(v); + } + }, -/** - * 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) - */ -Html5.canPlaySource = function (srcObj, options) { - return Html5.canPlayType(srcObj.type); -}; + textOpacity: { + selector: '.vjs-text-opacity > select', + id: 'captions-foreground-opacity-%s', + label: 'Transparency', + options: [OPACITY_OPAQUE, OPACITY_SEMI] + }, -/** - * 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} - * - True if volume can be controlled - * - False otherwise - */ -Html5.canControlVolume = function () { - // IE will error if Windows Media Player not installed #3315 - try { - var volume = Html5.TEST_VID.volume; + // Options for this object are defined below. + windowColor: { + selector: '.vjs-window-color > select', + id: 'captions-window-color-%s', + label: 'Color' + }, - Html5.TEST_VID.volume = volume / 2 + 0.1; - return volume !== Html5.TEST_VID.volume; - } catch (e) { - return false; + // Options for this object are defined below. + windowOpacity: { + selector: '.vjs-window-opacity > select', + id: 'captions-window-opacity-%s', + label: 'Transparency', + options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE] } }; +selectConfigs.windowColor.options = selectConfigs.backgroundColor.options; + /** - * Check if the playback rate can be changed in this browser/device. + * Get the actual value of an option. * - * @return {boolean} - * - True if playback rate can be controlled - * - False otherwise + * @param {string} value + * The value to get + * + * @param {Function} [parser] + * Optional function to adjust the value. + * + * @return {Mixed} + * - Will be `undefined` if no value exists + * - Will be `undefined` if the given value is "none". + * - Will be the actual value otherwise. + * + * @private */ -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 && browser.CHROME_VERSION < 58) { - return false; +function parseOptionValue(value, parser) { + if (parser) { + value = parser(value); } - // 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; + if (value && value !== 'none') { + return value; } -}; +} /** - * Check to see if native `TextTrack`s are supported by this browser/device. + * Gets the value of the selected '].join(''); + })).concat('').join(''); + }; - var sources = el.querySelectorAll('source'); - var i = sources.length; + /** + * Create foreground color element for the component + * + * @return {string} + * An HTML string. + * + * @private + */ - while (i--) { - el.removeChild(sources[i]); - } - // remove any src reference. - // not setting `src=''` because that throws an error - el.removeAttribute('src'); + TextTrackSettings.prototype.createElFgColor_ = function createElFgColor_() { + var legendId = 'captions-text-legend-' + this.id_; - if (typeof el.load === 'function') { - // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473) - (function () { - try { - el.load(); - } catch (e) { - // satisfy linter - } - })(); - } -}; + return ['
    ', '', this.localize('Text'), '', this.createElSelect_('color', legendId), '', this.createElSelect_('textOpacity', legendId), '', '
    '].join(''); + }; -/* Native HTML5 element property wrapping ----------------------------------- */ -// Wrap native properties with a getter -[ -/** - * Get the value of `paused` from the media element. `paused` indicates whether the media element - * is currently paused or not. - * - * @method Html5#paused - * @return {boolean} - * The value of `paused` from the media element. - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused} - */ -'paused', + /** + * Create background color element for the component + * + * @return {string} + * An HTML string. + * + * @private + */ -/** - * Get the value of `currentTime` from the media element. `currentTime` indicates - * the current second that the media is at in playback. - * - * @method Html5#currentTime - * @return {number} - * The value of `currentTime` from the media element. - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime} - */ -'currentTime', -/** - * Get the value of `buffered` from the media element. `buffered` is a `TimeRange` - * object that represents the parts of the media that are already downloaded and - * available for playback. - * - * @method Html5#buffered - * @return {TimeRange} - * The value of `buffered` from the media element. - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered} - */ -'buffered', + TextTrackSettings.prototype.createElBgColor_ = function createElBgColor_() { + var legendId = 'captions-background-' + this.id_; -/** - * Get the value of `volume` from the media element. `volume` indicates - * the current playback volume of audio for a media. `volume` will be a value from 0 - * (silent) to 1 (loudest and default). - * - * @method Html5#volume - * @return {number} - * The value of `volume` from the media element. Value will be between 0-1. - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume} - */ -'volume', + return ['
    ', '', this.localize('Background'), '', this.createElSelect_('backgroundColor', legendId), '', this.createElSelect_('backgroundOpacity', legendId), '', '
    '].join(''); + }; -/** - * Get the value of `muted` from the media element. `muted` indicates - * that the volume for the media should be set to silent. This does not actually change - * the `volume` attribute. - * - * @method Html5#muted - * @return {boolean} - * - True if the value of `volume` should be ignored and the audio set to silent. - * - False if the value of `volume` should be used. - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted} - */ -'muted', + /** + * Create window color element for the component + * + * @return {string} + * An HTML string. + * + * @private + */ -/** - * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates - * that the volume for the media should be set to silent when the video first starts. - * This does not actually change the `volume` attribute. After playback has started `muted` - * will indicate the current status of the volume and `defaultMuted` will not. - * - * @method Html5.prototype.defaultMuted - * @return {boolean} - * - True if the value of `volume` should be ignored and the audio set to silent. - * - False if the value of `volume` should be used. - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted} - */ -'defaultMuted', -/** - * Get the value of `poster` from the media element. `poster` indicates - * that the url of an image file that can/will be shown when no media data is available. - * - * @method Html5#poster - * @return {string} - * The value of `poster` from the media element. Value will be a url to an - * image. - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster} - */ -'poster', + TextTrackSettings.prototype.createElWinColor_ = function createElWinColor_() { + var legendId = 'captions-window-' + this.id_; -/** - * Get the value of `preload` from the media element. `preload` indicates - * what should download before the media is interacted with. It can have the following - * values: - * - none: nothing should be downloaded - * - metadata: poster and the first few frames of the media may be downloaded to get - * media dimensions and other metadata - * - auto: allow the media and metadata for the media to be downloaded before - * interaction - * - * @method Html5#preload - * @return {string} - * The value of `preload` from the media element. Will be 'none', 'metadata', - * or 'auto'. - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload} - */ -'preload', + return ['
    ', '', this.localize('Window'), '', this.createElSelect_('windowColor', legendId), '', this.createElSelect_('windowOpacity', legendId), '', '
    '].join(''); + }; -/** - * Get the value of `autoplay` from the media element. `autoplay` indicates - * that the media should start to play as soon as the page is ready. - * - * @method Html5#autoplay - * @return {boolean} - * - The value of `autoplay` from the media element. - * - True indicates that the media should start as soon as the page loads. - * - False indicates that the media should not start as soon as the page loads. - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay} - */ -'autoplay', + /** + * Create color elements for the component + * + * @return {Element} + * The element that was created + * + * @private + */ -/** - * Get the value of `controls` from the media element. `controls` indicates - * whether the native media controls should be shown or hidden. - * - * @method Html5#controls - * @return {boolean} - * - The value of `controls` from the media element. - * - True indicates that native controls should be showing. - * - False indicates that native controls should be hidden. - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls} - */ -'controls', -/** - * Get the value of `loop` from the media element. `loop` indicates - * that the media should return to the start of the media and continue playing once - * it reaches the end. - * - * @method Html5#loop - * @return {boolean} - * - The value of `loop` from the media element. - * - True indicates that playback should seek back to start once - * the end of a media is reached. - * - False indicates that playback should not loop back to the start when the - * end of the media is reached. - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop} - */ -'loop', + TextTrackSettings.prototype.createElColors_ = function createElColors_() { + return createEl('div', { + className: 'vjs-track-settings-colors', + innerHTML: [this.createElFgColor_(), this.createElBgColor_(), this.createElWinColor_()].join('') + }); + }; -/** - * Get the value of the `error` from the media element. `error` indicates any - * MediaError that may have occured during playback. If error returns null there is no - * current error. - * - * @method Html5#error - * @return {MediaError|null} - * The value of `error` from the media element. Will be `MediaError` if there - * is a current error and null otherwise. - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error} - */ -'error', + /** + * Create font elements for the component + * + * @return {Element} + * The element that was created. + * + * @private + */ -/** - * Get the value of `seeking` from the media element. `seeking` indicates whether the - * media is currently seeking to a new position or not. - * - * @method Html5#seeking - * @return {boolean} - * - The value of `seeking` from the media element. - * - True indicates that the media is currently seeking to a new position. - * - Flase indicates that the media is not seeking to a new position at this time. - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking} - */ -'seeking', -/** - * Get the value of `seekable` from the media element. `seekable` returns a - * `TimeRange` object indicating ranges of time that can currently be `seeked` to. - * - * @method Html5#seekable - * @return {TimeRange} - * The value of `seekable` from the media element. A `TimeRange` object - * indicating the current ranges of time that can be seeked to. - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable} - */ -'seekable', + TextTrackSettings.prototype.createElFont_ = function createElFont_() { + return createEl('div', { + className: 'vjs-track-settings-font', + innerHTML: ['
    ', this.createElSelect_('fontPercent', '', 'legend'), '
    ', '
    ', this.createElSelect_('edgeStyle', '', 'legend'), '
    ', '
    ', this.createElSelect_('fontFamily', '', 'legend'), '
    '].join('') + }); + }; -/** - * Get the value of `ended` from the media element. `ended` indicates whether - * the media has reached the end or not. - * - * @method Html5#ended - * @return {boolean} - * - The value of `ended` from the media element. - * - True indicates that the media has ended. - * - False indicates that the media has not ended. - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended} - */ -'ended', + /** + * Create controls for the component + * + * @return {Element} + * The element that was created. + * + * @private + */ -/** - * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates - * whether the media should start muted or not. Only changes the default state of the - * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the - * current state. - * - * @method Html5#defaultMuted - * @return {boolean} - * - The value of `defaultMuted` from the media element. - * - True indicates that the media should start muted. - * - False indicates that the media should not start muted - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted} - */ -'defaultMuted', -/** - * Get the value of `playbackRate` from the media element. `playbackRate` indicates - * the rate at which the media is currently playing back. Examples: - * - if playbackRate is set to 2, media will play twice as fast. - * - if playbackRate is set to 0.5, media will play half as fast. - * - * @method Html5#playbackRate - * @return {number} - * The value of `playbackRate` from the media element. A number indicating - * the current playback speed of the media, where 1 is normal speed. - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} - */ -'playbackRate', - -/** - * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates - * the rate at which the media is currently playing back. This value will not indicate the current - * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that. - * - * Examples: - * - if defaultPlaybackRate is set to 2, media will play twice as fast. - * - if defaultPlaybackRate is set to 0.5, media will play half as fast. - * - * @method Html5.prototype.defaultPlaybackRate - * @return {number} - * The value of `defaultPlaybackRate` from the media element. A number indicating - * the current playback speed of the media, where 1 is normal speed. - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} - */ -'defaultPlaybackRate', + TextTrackSettings.prototype.createElControls_ = function createElControls_() { + var defaultsDescription = this.localize('restore all settings to the default values'); -/** - * Get the value of `played` from the media element. `played` returns a `TimeRange` - * object representing points in the media timeline that have been played. - * - * @method Html5#played - * @return {TimeRange} - * The value of `played` from the media element. A `TimeRange` object indicating - * the ranges of time that have been played. - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played} - */ -'played', + return createEl('div', { + className: 'vjs-track-settings-controls', + innerHTML: ['', ''].join('') + }); + }; -/** - * Get the value of `networkState` from the media element. `networkState` indicates - * the current network state. It returns an enumeration from the following list: - * - 0: NETWORK_EMPTY - * - 1: NEWORK_IDLE - * - 2: NETWORK_LOADING - * - 3: NETWORK_NO_SOURCE - * - * @method Html5#networkState - * @return {number} - * The value of `networkState` from the media element. This will be a number - * from the list in the description. - * - * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate} - */ -'networkState', + TextTrackSettings.prototype.content = function content() { + return [this.createElColors_(), this.createElFont_(), this.createElControls_()]; + }; -/** - * Get the value of `readyState` from the media element. `readyState` indicates - * the current state of the media element. It returns an enumeration from the - * following list: - * - 0: HAVE_NOTHING - * - 1: HAVE_METADATA - * - 2: HAVE_CURRENT_DATA - * - 3: HAVE_FUTURE_DATA - * - 4: HAVE_ENOUGH_DATA - * - * @method Html5#readyState - * @return {number} - * The value of `readyState` from the media element. This will be a number - * from the list in the description. - * - * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states} - */ -'readyState', + TextTrackSettings.prototype.label = function label() { + return this.localize('Caption Settings Dialog'); + }; -/** - * Get the value of `videoWidth` from the video element. `videoWidth` indicates - * the current width of the video in css pixels. - * - * @method Html5#videoWidth - * @return {number} - * The value of `videoWidth` from the video element. This will be a number - * in css pixels. - * - * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth} - */ -'videoWidth', + TextTrackSettings.prototype.description = function description() { + return this.localize('Beginning of dialog window. Escape will cancel and close the window.'); + }; -/** - * Get the value of `videoHeight` from the video element. `videoHeigth` indicates - * the current height of the video in css pixels. - * - * @method Html5#videoHeight - * @return {number} - * The value of `videoHeight` from the video element. This will be a number - * in css pixels. - * - * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth} - */ -'videoHeight'].forEach(function (prop) { - Html5.prototype[prop] = function () { - return this.el_[prop]; + TextTrackSettings.prototype.buildCSSClass = function buildCSSClass() { + return _ModalDialog.prototype.buildCSSClass.call(this) + ' vjs-text-track-settings'; }; -}); -// Wrap native properties with a setter in this format: -// set + toTitleCase(name) -[ -/** - * Set the value of `volume` on the media element. `volume` indicates the current - * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and - * so on. - * - * @method Html5#setVolume - * @param {number} percentAsDecimal - * The volume percent as a decimal. Valid range is from 0-1. - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume} - */ -'volume', + /** + * Gets an object of text track settings (or null). + * + * @return {Object} + * An object with config values parsed from the DOM or localStorage. + */ -/** - * Set the value of `muted` on the media element. `muted` indicates that the current - * audio level should be silent. - * - * @method Html5#setMuted - * @param {boolean} muted - * - True if the audio should be set to silent - * - False otherwise - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted} - */ -'muted', -/** - * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current - * audio level should be silent, but will only effect the muted level on intial playback.. - * - * @method Html5.prototype.setDefaultMuted - * @param {boolean} defaultMuted - * - True if the audio should be set to silent - * - False otherwise - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted} - */ -'defaultMuted', + TextTrackSettings.prototype.getValues = function getValues() { + var _this3 = this; -/** - * Set the value of `src` on the media element. `src` indicates the current - * {@link Tech~SourceObject} for the media. - * - * @method Html5#setSrc - * @param {Tech~SourceObject} src - * The source object to set as the current source. - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src} - */ -'src', + return reduce(selectConfigs, function (accum, config, key) { + var value = getSelectedOptionValue(_this3.$(config.selector), config.parser); -/** - * Set the value of `poster` on the media element. `poster` is the url to - * an image file that can/will be shown when no media data is available. - * - * @method Html5#setPoster - * @param {string} poster - * The url to an image that should be used as the `poster` for the media - * element. - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster} - */ -'poster', + if (value !== undefined) { + accum[key] = value; + } -/** - * Set the value of `preload` on the media element. `preload` indicates - * what should download before the media is interacted with. It can have the following - * values: - * - none: nothing should be downloaded - * - metadata: poster and the first few frames of the media may be downloaded to get - * media dimensions and other metadata - * - auto: allow the media and metadata for the media to be downloaded before - * interaction - * - * @method Html5#setPreload - * @param {string} preload - * The value of `preload` to set on the media element. Must be 'none', 'metadata', - * or 'auto'. - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload} - */ -'preload', + return accum; + }, {}); + }; -/** - * Set the value of `autoplay` on the media element. `autoplay` indicates - * that the media should start to play as soon as the page is ready. - * - * @method Html5#setAutoplay - * @param {boolean} autoplay - * - True indicates that the media should start as soon as the page loads. - * - False indicates that the media should not start as soon as the page loads. - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay} - */ -'autoplay', + /** + * Sets text track settings from an object of values. + * + * @param {Object} values + * An object with config values parsed from the DOM or localStorage. + */ -/** - * Set the value of `loop` on the media element. `loop` indicates - * that the media should return to the start of the media and continue playing once - * it reaches the end. - * - * @method Html5#setLoop - * @param {boolean} loop - * - True indicates that playback should seek back to start once - * the end of a media is reached. - * - False indicates that playback should not loop back to the start when the - * end of the media is reached. - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop} - */ -'loop', -/** - * Set the value of `playbackRate` on the media element. `playbackRate` indicates - * the rate at which the media should play back. Examples: - * - if playbackRate is set to 2, media will play twice as fast. - * - if playbackRate is set to 0.5, media will play half as fast. - * - * @method Html5#setPlaybackRate - * @return {number} - * The value of `playbackRate` from the media element. A number indicating - * the current playback speed of the media, where 1 is normal speed. - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} - */ -'playbackRate', - -/** - * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates - * the rate at which the media should play back upon initial startup. Changing this value - * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}. - * - * Example Values: - * - if playbackRate is set to 2, media will play twice as fast. - * - if playbackRate is set to 0.5, media will play half as fast. - * - * @method Html5.prototype.setDefaultPlaybackRate - * @return {number} - * The value of `defaultPlaybackRate` from the media element. A number indicating - * the current playback speed of the media, where 1 is normal speed. - * - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate} - */ -'defaultPlaybackRate'].forEach(function (prop) { - Html5.prototype['set' + (0, _toTitleCase2['default'])(prop)] = function (v) { - this.el_[prop] = v; - }; -}); - -// wrap native functions with a function -[ -/** - * A wrapper around the media elements `pause` function. This will call the `HTML5` - * media elements `pause` function. - * - * @method Html5#pause - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause} - */ -'pause', - -/** - * A wrapper around the media elements `load` function. This will call the `HTML5`s - * media element `load` function. - * - * @method Html5#load - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load} - */ -'load', + TextTrackSettings.prototype.setValues = function setValues(values) { + var _this4 = this; -/** - * A wrapper around the media elements `play` function. This will call the `HTML5`s - * media element `play` function. - * - * @method Html5#play - * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play} - */ -'play'].forEach(function (prop) { - Html5.prototype[prop] = function () { - return this.el_[prop](); + each(selectConfigs, function (config, key) { + setSelectedOption(_this4.$(config.selector), values[key], config.parser); + }); }; -}); - -_tech2['default'].withSourceHandlers(Html5); -/** - * Native source handler for Html5, simply passes the source to the media element. - * - * @proprety {Tech~SourceObject} source - * The source object - * - * @proprety {Html5} tech - * The instance of the HTML5 tech. - */ -Html5.nativeSourceHandler = {}; + /** + * Sets all ` elements in the DOM of this component. -// -// Possible keys include: -// -// `default`: -// The default option index. Only needs to be provided if not zero. -// `parser`: -// A function which is used to parse the value from the selected option in -// a customized way. -// `selector`: -// The selector used to find the associated element. - * - * @param {Element} el - * the element to look in - * - * @param {Function} [parser] - * Optional function to adjust the value. - * - * @return {Mixed} - * - Will be `undefined` if no value exists - * - Will be `undefined` if the given value is "none". - * - Will be the actual value otherwise. - * - * @private - */ -function getSelectedOptionValue(el, parser) { - var value = el.options[el.options.selectedIndex].value; - - return parseOptionValue(value, parser); -} - -/** - * Sets the selected