videojs_rails 4.9.1 → 4.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 99287eb1d4e6a6471da87b300cf6cc6a1a02b021
4
- data.tar.gz: 9b9fb338831a59417338dd945eafba11547359b9
3
+ metadata.gz: 53ac8c4527a2f49acbf718f4598e0e244bda1bd4
4
+ data.tar.gz: 7a173b8a8eb80952698a749455ffd7f9dfabceaa
5
5
  SHA512:
6
- metadata.gz: 2602d3c752c79dd03a5347e5db1041a13b20eeb481b650b8bfe4bbe1b18694e9cad30413b30a8886527f5fe68fc9084fd81019153ad439245eb9fd730edf0e88
7
- data.tar.gz: 34745609b0148fe1d3ce21fe6b813e6ce5970500dd91b54a71f5c5ede2a4ba47f1c78e57a2dd79a18efec26904c3bbbeb5f708e9e21a31a8bde85445ff8e3af0
6
+ metadata.gz: 1d944e69a7484274035c4552e3624104fb2f28fbf857aa4b2915c83e6bc48b3ab53b17a792ac15f45718afdaf1dc2bb5e7a2abeead6fef63d40f11010917900b
7
+ data.tar.gz: 8bcccdbabe88e70f96b9fa4cdc110f8f29d84291e350c744e761a5176661c8c83ba5296405752b76c2204da315e53d0b71eb6a443c960012fa295009dcdd5eba
@@ -1,3 +1,3 @@
1
1
  module VideojsRails
2
- VERSION = '4.9.1'
2
+ VERSION = '4.10.0'
3
3
  end
@@ -63,7 +63,7 @@ var vjs = function(id, options, ready){
63
63
  var videojs = window['videojs'] = vjs;
64
64
 
65
65
  // CDN Version. Used to target right flash swf.
66
- vjs.CDN_VERSION = '4.9';
66
+ vjs.CDN_VERSION = '4.10';
67
67
  vjs.ACCESS_PROTOCOL = ('https:' == document.location.protocol ? 'https://' : 'http://');
68
68
 
69
69
  /**
@@ -116,7 +116,7 @@ vjs.options = {
116
116
  };
117
117
 
118
118
  // Set CDN Version of swf
119
- // The added (+) blocks the replace from changing this 4.9 string
119
+ // The added (+) blocks the replace from changing this 4.10 string
120
120
  if (vjs.CDN_VERSION !== 'GENERATED'+'_CDN_VSN') {
121
121
  videojs.options['flash']['swf'] = "<%= asset_path('video-js.swf') %>";
122
122
  }
@@ -360,7 +360,7 @@ vjs.on = function(elem, type, fn){
360
360
  * Removes event listeners from an element
361
361
  * @param {Element|Object} elem Object to remove listeners from
362
362
  * @param {String|Array=} type Type of listener to remove. Don't include to remove all events from element.
363
- * @param {Function} fn Specific listener to remove. Don't incldue to remove listeners for an event type.
363
+ * @param {Function} fn Specific listener to remove. Don't include to remove listeners for an event type.
364
364
  * @private
365
365
  */
366
366
  vjs.off = function(elem, type, fn) {
@@ -479,7 +479,7 @@ vjs.fixEvent = function(event) {
479
479
  for (var key in old) {
480
480
  // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
481
481
  // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation
482
- if (key !== 'layerX' && key !== 'layerY' && key !== 'keyboardEvent.keyLocation') {
482
+ if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation') {
483
483
  // Chrome 32+ warns if you try to copy deprecated returnValue, but
484
484
  // we still want to if preventDefault isn't supported (IE8).
485
485
  if (!(key == 'returnValue' && old.preventDefault)) {
@@ -1080,6 +1080,7 @@ vjs.IS_FIREFOX = (/Firefox/i).test(vjs.USER_AGENT);
1080
1080
  vjs.IS_CHROME = (/Chrome/i).test(vjs.USER_AGENT);
1081
1081
 
1082
1082
  vjs.TOUCH_ENABLED = !!(('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch);
1083
+ vjs.BACKGROUND_SIZE_SUPPORTED = 'backgroundSize' in vjs.TEST_VID.style;
1083
1084
 
1084
1085
  /**
1085
1086
  * Apply attributes to an HTML element.
@@ -1662,8 +1663,14 @@ vjs.Component = vjs.CoreObject.extend({
1662
1663
  // Updated options with supplied options
1663
1664
  options = this.options(options);
1664
1665
 
1665
- // Get ID from options, element, or create using player ID and unique ID
1666
- this.id_ = options['id'] || ((options['el'] && options['el']['id']) ? options['el']['id'] : player.id() + '_component_' + vjs.guid++ );
1666
+ // Get ID from options or options element if one is supplied
1667
+ this.id_ = options['id'] || (options['el'] && options['el']['id']);
1668
+
1669
+ // If there was no ID from the options, generate one
1670
+ if (!this.id_) {
1671
+ // Don't require the player ID function in the case of mock players
1672
+ this.id_ = ((player.id && player.id()) || 'no_player') + '_component_' + vjs.guid++;
1673
+ }
1667
1674
 
1668
1675
  this.name_ = options['name'] || null;
1669
1676
 
@@ -1969,17 +1976,17 @@ vjs.Component.prototype.getChild = function(name){
1969
1976
  * @suppress {accessControls|checkRegExp|checkTypes|checkVars|const|constantProperty|deprecated|duplicate|es5Strict|fileoverviewTags|globalThis|invalidCasts|missingProperties|nonStandardJsDocs|strictModuleDepCheck|undefinedNames|undefinedVars|unknownDefines|uselessCode|visibility}
1970
1977
  */
1971
1978
  vjs.Component.prototype.addChild = function(child, options){
1972
- var component, componentClass, componentName, componentId;
1979
+ var component, componentClass, componentName;
1973
1980
 
1974
- // If string, create new component with options
1981
+ // If child is a string, create new component with options
1975
1982
  if (typeof child === 'string') {
1976
-
1977
1983
  componentName = child;
1978
1984
 
1979
1985
  // Make sure options is at least an empty object to protect against errors
1980
1986
  options = options || {};
1981
1987
 
1982
- // Assume name of set is a lowercased name of the UI Class (PlayButton, etc.)
1988
+ // If no componentClass in options, assume componentClass is the name lowercased
1989
+ // (e.g. playButton)
1983
1990
  componentClass = options['componentClass'] || vjs.capitalize(componentName);
1984
1991
 
1985
1992
  // Set name through options
@@ -2088,36 +2095,51 @@ vjs.Component.prototype.removeChild = function(component){
2088
2095
  *
2089
2096
  */
2090
2097
  vjs.Component.prototype.initChildren = function(){
2091
- var parent, children, child, name, opts;
2098
+ var parent, parentOptions, children, child, name, opts, handleAdd;
2092
2099
 
2093
2100
  parent = this;
2094
- children = this.options()['children'];
2101
+ parentOptions = parent.options();
2102
+ children = parentOptions['children'];
2095
2103
 
2096
2104
  if (children) {
2105
+ handleAdd = function(name, opts){
2106
+ // Allow options for children to be set at the parent options
2107
+ // e.g. videojs(id, { controlBar: false });
2108
+ // instead of videojs(id, { children: { controlBar: false });
2109
+ if (parentOptions[name]) {
2110
+ opts = parentOptions[name];
2111
+ }
2112
+
2113
+ // Allow for disabling default components
2114
+ // e.g. vjs.options['children']['posterImage'] = false
2115
+ if (opts === false) return;
2116
+
2117
+ // Create and add the child component.
2118
+ // Add a direct reference to the child by name on the parent instance.
2119
+ // If two of the same component are used, different names should be supplied
2120
+ // for each
2121
+ parent[name] = parent.addChild(name, opts);
2122
+ };
2123
+
2097
2124
  // Allow for an array of children details to passed in the options
2098
2125
  if (vjs.obj.isArray(children)) {
2099
2126
  for (var i = 0; i < children.length; i++) {
2100
2127
  child = children[i];
2101
2128
 
2102
2129
  if (typeof child == 'string') {
2130
+ // ['myComponent']
2103
2131
  name = child;
2104
2132
  opts = {};
2105
2133
  } else {
2134
+ // [{ name: 'myComponent', otherOption: true }]
2106
2135
  name = child.name;
2107
2136
  opts = child;
2108
2137
  }
2109
2138
 
2110
- parent[name] = parent.addChild(name, opts);
2139
+ handleAdd(name, opts);
2111
2140
  }
2112
2141
  } else {
2113
- vjs.obj.each(children, function(name, opts){
2114
- // Allow for disabling default components
2115
- // e.g. vjs.options['children']['posterImage'] = false
2116
- if (opts === false) return;
2117
-
2118
- // Set property name on player. Could cause conflicts with other prop names, but it's worth making refs easy.
2119
- parent[name] = parent.addChild(name, opts);
2120
- });
2142
+ vjs.obj.each(children, handleAdd);
2121
2143
  }
2122
2144
  }
2123
2145
  };
@@ -2140,46 +2162,169 @@ vjs.Component.prototype.buildCSSClass = function(){
2140
2162
  * Add an event listener to this component's element
2141
2163
  *
2142
2164
  * var myFunc = function(){
2143
- * var myPlayer = this;
2165
+ * var myComponent = this;
2144
2166
  * // Do something when the event is fired
2145
2167
  * };
2146
2168
  *
2147
- * myPlayer.on("eventName", myFunc);
2169
+ * myComponent.on('eventType', myFunc);
2170
+ *
2171
+ * The context of myFunc will be myComponent unless previously bound.
2172
+ *
2173
+ * Alternatively, you can add a listener to another element or component.
2174
+ *
2175
+ * myComponent.on(otherElement, 'eventName', myFunc);
2176
+ * myComponent.on(otherComponent, 'eventName', myFunc);
2177
+ *
2178
+ * The benefit of using this over `vjs.on(otherElement, 'eventName', myFunc)`
2179
+ * and `otherComponent.on('eventName', myFunc)` is that this way the listeners
2180
+ * will be automatically cleaned up when either component is diposed.
2181
+ * It will also bind myComponent as the context of myFunc.
2148
2182
  *
2149
- * The context will be the component.
2183
+ * **NOTE**: When using this on elements in the page other than window
2184
+ * and document (both permanent), if you remove the element from the DOM
2185
+ * you need to call `vjs.trigger(el, 'dispose')` on it to clean up
2186
+ * references to it and allow the browser to garbage collect it.
2150
2187
  *
2151
- * @param {String} type The event type e.g. 'click'
2152
- * @param {Function} fn The event listener
2153
- * @return {vjs.Component} self
2188
+ * @param {String|vjs.Component} first The event type or other component
2189
+ * @param {Function|String} second The event handler or event type
2190
+ * @param {Function} third The event handler
2191
+ * @return {vjs.Component} self
2154
2192
  */
2155
- vjs.Component.prototype.on = function(type, fn){
2156
- vjs.on(this.el_, type, vjs.bind(this, fn));
2193
+ vjs.Component.prototype.on = function(first, second, third){
2194
+ var target, type, fn, removeOnDispose, cleanRemover, thisComponent;
2195
+
2196
+ if (typeof first === 'string' || vjs.obj.isArray(first)) {
2197
+ vjs.on(this.el_, first, vjs.bind(this, second));
2198
+
2199
+ // Targeting another component or element
2200
+ } else {
2201
+ target = first;
2202
+ type = second;
2203
+ fn = vjs.bind(this, third);
2204
+ thisComponent = this;
2205
+
2206
+ // When this component is disposed, remove the listener from the other component
2207
+ removeOnDispose = function(){
2208
+ thisComponent.off(target, type, fn);
2209
+ };
2210
+ // Use the same function ID so we can remove it later it using the ID
2211
+ // of the original listener
2212
+ removeOnDispose.guid = fn.guid;
2213
+ this.on('dispose', removeOnDispose);
2214
+
2215
+ // If the other component is disposed first we need to clean the reference
2216
+ // to the other component in this component's removeOnDispose listener
2217
+ // Otherwise we create a memory leak.
2218
+ cleanRemover = function(){
2219
+ thisComponent.off('dispose', removeOnDispose);
2220
+ };
2221
+ // Add the same function ID so we can easily remove it later
2222
+ cleanRemover.guid = fn.guid;
2223
+
2224
+ // Check if this is a DOM node
2225
+ if (first.nodeName) {
2226
+ // Add the listener to the other element
2227
+ vjs.on(target, type, fn);
2228
+ vjs.on(target, 'dispose', cleanRemover);
2229
+
2230
+ // Should be a component
2231
+ // Not using `instanceof vjs.Component` because it makes mock players difficult
2232
+ } else if (typeof first.on === 'function') {
2233
+ // Add the listener to the other component
2234
+ target.on(type, fn);
2235
+ target.on('dispose', cleanRemover);
2236
+ }
2237
+ }
2238
+
2157
2239
  return this;
2158
2240
  };
2159
2241
 
2160
2242
  /**
2161
- * Remove an event listener from the component's element
2243
+ * Remove an event listener from this component's element
2244
+ *
2245
+ * myComponent.off('eventType', myFunc);
2162
2246
  *
2163
- * myComponent.off("eventName", myFunc);
2247
+ * If myFunc is excluded, ALL listeners for the event type will be removed.
2248
+ * If eventType is excluded, ALL listeners will be removed from the component.
2164
2249
  *
2165
- * @param {String=} type Event type. Without type it will remove all listeners.
2166
- * @param {Function=} fn Event listener. Without fn it will remove all listeners for a type.
2250
+ * Alternatively you can use `off` to remove listeners that were added to other
2251
+ * elements or components using `myComponent.on(otherComponent...`.
2252
+ * In this case both the event type and listener function are REQUIRED.
2253
+ *
2254
+ * myComponent.off(otherElement, 'eventType', myFunc);
2255
+ * myComponent.off(otherComponent, 'eventType', myFunc);
2256
+ *
2257
+ * @param {String=|vjs.Component} first The event type or other component
2258
+ * @param {Function=|String} second The listener function or event type
2259
+ * @param {Function=} third The listener for other component
2167
2260
  * @return {vjs.Component}
2168
2261
  */
2169
- vjs.Component.prototype.off = function(type, fn){
2170
- vjs.off(this.el_, type, fn);
2262
+ vjs.Component.prototype.off = function(first, second, third){
2263
+ var target, otherComponent, type, fn, otherEl;
2264
+
2265
+ if (!first || typeof first === 'string' || vjs.obj.isArray(first)) {
2266
+ vjs.off(this.el_, first, second);
2267
+ } else {
2268
+ target = first;
2269
+ type = second;
2270
+ // Ensure there's at least a guid, even if the function hasn't been used
2271
+ fn = vjs.bind(this, third);
2272
+
2273
+ // Remove the dispose listener on this component,
2274
+ // which was given the same guid as the event listener
2275
+ this.off('dispose', fn);
2276
+
2277
+ if (first.nodeName) {
2278
+ // Remove the listener
2279
+ vjs.off(target, type, fn);
2280
+ // Remove the listener for cleaning the dispose listener
2281
+ vjs.off(target, 'dispose', fn);
2282
+ } else {
2283
+ target.off(type, fn);
2284
+ target.off('dispose', fn);
2285
+ }
2286
+ }
2287
+
2171
2288
  return this;
2172
2289
  };
2173
2290
 
2174
2291
  /**
2175
2292
  * Add an event listener to be triggered only once and then removed
2176
2293
  *
2177
- * @param {String} type Event type
2178
- * @param {Function} fn Event listener
2294
+ * myComponent.one('eventName', myFunc);
2295
+ *
2296
+ * Alternatively you can add a listener to another element or component
2297
+ * that will be triggered only once.
2298
+ *
2299
+ * myComponent.one(otherElement, 'eventName', myFunc);
2300
+ * myComponent.one(otherComponent, 'eventName', myFunc);
2301
+ *
2302
+ * @param {String|vjs.Component} first The event type or other component
2303
+ * @param {Function|String} second The listener function or event type
2304
+ * @param {Function=} third The listener function for other component
2179
2305
  * @return {vjs.Component}
2180
2306
  */
2181
- vjs.Component.prototype.one = function(type, fn) {
2182
- vjs.one(this.el_, type, vjs.bind(this, fn));
2307
+ vjs.Component.prototype.one = function(first, second, third) {
2308
+ var target, type, fn, thisComponent, newFunc;
2309
+
2310
+ if (typeof first === 'string' || vjs.obj.isArray(first)) {
2311
+ vjs.one(this.el_, first, vjs.bind(this, second));
2312
+ } else {
2313
+ target = first;
2314
+ type = second;
2315
+ fn = vjs.bind(this, third);
2316
+ thisComponent = this;
2317
+
2318
+ newFunc = function(){
2319
+ thisComponent.off(target, type, newFunc);
2320
+ fn.apply(this, arguments);
2321
+ };
2322
+ // Keep the same function ID so we can remove it later
2323
+ newFunc.guid = fn.guid;
2324
+
2325
+ this.on(target, type, newFunc);
2326
+ }
2327
+
2183
2328
  return this;
2184
2329
  };
2185
2330
 
@@ -2591,6 +2736,11 @@ vjs.Component.prototype.emitTapEvents = function(){
2591
2736
  vjs.Component.prototype.enableTouchActivity = function() {
2592
2737
  var report, touchHolding, touchEnd;
2593
2738
 
2739
+ // Don't continue if the root player doesn't support reporting user activity
2740
+ if (!this.player().reportUserActivity) {
2741
+ return;
2742
+ }
2743
+
2594
2744
  // listener for reporting that the user is active
2595
2745
  report = vjs.bind(this.player(), this.player().reportUserActivity);
2596
2746
 
@@ -2722,13 +2872,10 @@ vjs.Slider = vjs.Component.extend({
2722
2872
  this.on('blur', this.onBlur);
2723
2873
  this.on('click', this.onClick);
2724
2874
 
2725
- this.player_.on('controlsvisible', vjs.bind(this, this.update));
2726
-
2727
- player.on(this.playerEvent, vjs.bind(this, this.update));
2875
+ this.on(player, 'controlsvisible', this.update);
2876
+ this.on(player, this.playerEvent, this.update);
2728
2877
 
2729
2878
  this.boundEvents = {};
2730
-
2731
-
2732
2879
  this.boundEvents.move = vjs.bind(this, this.onMouseMove);
2733
2880
  this.boundEvents.end = vjs.bind(this, this.onMouseUp);
2734
2881
  }
@@ -3368,9 +3515,10 @@ vjs.Player = vjs.Component.extend({
3368
3515
  this.cache_ = {};
3369
3516
 
3370
3517
  // Set poster
3371
- this.poster_ = options['poster'];
3518
+ this.poster_ = options['poster'] || '';
3519
+
3372
3520
  // Set controls
3373
- this.controls_ = options['controls'];
3521
+ this.controls_ = !!options['controls'];
3374
3522
  // Original tag settings stored in options
3375
3523
  // now remove immediately so native controls don't flash.
3376
3524
  // May be turned back on by HTML5 tech if nativeControlsForTouch is true
@@ -3592,6 +3740,10 @@ vjs.Player.prototype.createEl = function(){
3592
3740
  this.width(this.options_['width'], true); // (true) Skip resize listener on load
3593
3741
  this.height(this.options_['height'], true);
3594
3742
 
3743
+ // vjs.insertFirst seems to cause the networkState to flicker from 3 to 2, so
3744
+ // keep track of the original for later so we can know if the source originally failed
3745
+ tag.initNetworkState_ = tag.networkState;
3746
+
3595
3747
  // Wrap video tag in div (el/box) container
3596
3748
  if (tag.parentNode) {
3597
3749
  tag.parentNode.insertBefore(el, tag);
@@ -3772,6 +3924,7 @@ vjs.Player.prototype.onWaiting = function(){
3772
3924
  /**
3773
3925
  * A handler for events that signal that waiting has eneded
3774
3926
  * which is not consistent between browsers. See #1351
3927
+ * @private
3775
3928
  */
3776
3929
  vjs.Player.prototype.onWaitEnd = function(){
3777
3930
  this.removeClass('vjs-waiting');
@@ -4050,7 +4203,14 @@ vjs.Player.prototype.duration = function(seconds){
4050
4203
  return this.cache_.duration || 0;
4051
4204
  };
4052
4205
 
4053
- // Calculates how much time is left. Not in spec, but useful.
4206
+ /**
4207
+ * Calculates how much time is left.
4208
+ *
4209
+ * var timeLeft = myPlayer.remainingTime();
4210
+ *
4211
+ * Not a native video element function, but useful
4212
+ * @return {Number} The time remaining in seconds
4213
+ */
4054
4214
  vjs.Player.prototype.remainingTime = function(){
4055
4215
  return this.duration() - this.currentTime();
4056
4216
  };
@@ -4522,14 +4682,20 @@ vjs.Player.prototype.sourceList_ = function(sources){
4522
4682
  }
4523
4683
  };
4524
4684
 
4525
- // Begin loading the src data
4526
- // http://dev.w3.org/html5/spec/video.html#dom-media-load
4685
+ /**
4686
+ * Begin loading the src data.
4687
+ * @return {vjs.Player} Returns the player
4688
+ */
4527
4689
  vjs.Player.prototype.load = function(){
4528
4690
  this.techCall('load');
4529
4691
  return this;
4530
4692
  };
4531
4693
 
4532
- // http://dev.w3.org/html5/spec/video.html#dom-media-currentsrc
4694
+ /**
4695
+ * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4
4696
+ * Can be used in conjuction with `currentType` to assist in rebuilding the current source object.
4697
+ * @return {String} The current source
4698
+ */
4533
4699
  vjs.Player.prototype.currentSrc = function(){
4534
4700
  return this.techGet('currentSrc') || this.cache_.src || '';
4535
4701
  };
@@ -4544,7 +4710,11 @@ vjs.Player.prototype.currentType = function(){
4544
4710
  return this.currentType_ || '';
4545
4711
  };
4546
4712
 
4547
- // Attributes/Options
4713
+ /**
4714
+ * Get or set the preload attribute.
4715
+ * @return {String} The preload attribute value when getting
4716
+ * @return {vjs.Player} Returns the player when setting
4717
+ */
4548
4718
  vjs.Player.prototype.preload = function(value){
4549
4719
  if (value !== undefined) {
4550
4720
  this.techCall('setPreload', value);
@@ -4553,6 +4723,12 @@ vjs.Player.prototype.preload = function(value){
4553
4723
  }
4554
4724
  return this.techGet('preload');
4555
4725
  };
4726
+
4727
+ /**
4728
+ * Get or set the autoplay attribute.
4729
+ * @return {String} The autoplay attribute value when getting
4730
+ * @return {vjs.Player} Returns the player when setting
4731
+ */
4556
4732
  vjs.Player.prototype.autoplay = function(value){
4557
4733
  if (value !== undefined) {
4558
4734
  this.techCall('setAutoplay', value);
@@ -4561,6 +4737,12 @@ vjs.Player.prototype.autoplay = function(value){
4561
4737
  }
4562
4738
  return this.techGet('autoplay', value);
4563
4739
  };
4740
+
4741
+ /**
4742
+ * Get or set the loop attribute on the video element.
4743
+ * @return {String} The loop attribute value when getting
4744
+ * @return {vjs.Player} Returns the player when setting
4745
+ */
4564
4746
  vjs.Player.prototype.loop = function(value){
4565
4747
  if (value !== undefined) {
4566
4748
  this.techCall('setLoop', value);
@@ -4597,6 +4779,12 @@ vjs.Player.prototype.poster = function(src){
4597
4779
  return this.poster_;
4598
4780
  }
4599
4781
 
4782
+ // The correct way to remove a poster is to set as an empty string
4783
+ // other falsey values will throw errors
4784
+ if (!src) {
4785
+ src = '';
4786
+ }
4787
+
4600
4788
  // update the internal poster variable
4601
4789
  this.poster_ = src;
4602
4790
 
@@ -4738,7 +4926,16 @@ vjs.Player.prototype.error = function(err){
4738
4926
  return this;
4739
4927
  };
4740
4928
 
4929
+ /**
4930
+ * Returns whether or not the player is in the "ended" state.
4931
+ * @return {Boolean} True if the player is in the ended state, false if not.
4932
+ */
4741
4933
  vjs.Player.prototype.ended = function(){ return this.techGet('ended'); };
4934
+
4935
+ /**
4936
+ * Returns whether or not the player is in the "seeking" state.
4937
+ * @return {Boolean} True if the player is in the seeking state, false if not.
4938
+ */
4742
4939
  vjs.Player.prototype.seeking = function(){ return this.techGet('seeking'); };
4743
4940
 
4744
4941
  // When the player is first initialized, trigger activity so components
@@ -4875,6 +5072,12 @@ vjs.Player.prototype.listenForUserActivity = function(){
4875
5072
  });
4876
5073
  };
4877
5074
 
5075
+ /**
5076
+ * Gets or sets the current playback rate.
5077
+ * @param {Boolean} rate New playback rate to set.
5078
+ * @return {Number} Returns the new playback rate when setting
5079
+ * @return {Number} Returns the current playback rate when getting
5080
+ */
4878
5081
  vjs.Player.prototype.playbackRate = function(rate) {
4879
5082
  if (rate !== undefined) {
4880
5083
  this.techCall('setPlaybackRate', rate);
@@ -4889,7 +5092,21 @@ vjs.Player.prototype.playbackRate = function(rate) {
4889
5092
 
4890
5093
  };
4891
5094
 
5095
+ /**
5096
+ * Store the current audio state
5097
+ * @type {Boolean}
5098
+ * @private
5099
+ */
4892
5100
  vjs.Player.prototype.isAudio_ = false;
5101
+
5102
+ /**
5103
+ * Gets or sets the audio flag
5104
+ *
5105
+ * @param {Boolean} bool True signals that this is an audio player.
5106
+ * @return {Boolean} Returns true if player is audio, false if not when getting
5107
+ * @return {vjs.Player} Returns the player if setting
5108
+ * @private
5109
+ */
4893
5110
  vjs.Player.prototype.isAudio = function(bool) {
4894
5111
  if (bool !== undefined) {
4895
5112
  this.isAudio_ = !!bool;
@@ -4991,8 +5208,8 @@ vjs.PlayToggle = vjs.Button.extend({
4991
5208
  init: function(player, options){
4992
5209
  vjs.Button.call(this, player, options);
4993
5210
 
4994
- player.on('play', vjs.bind(this, this.onPlay));
4995
- player.on('pause', vjs.bind(this, this.onPause));
5211
+ this.on(player, 'play', this.onPlay);
5212
+ this.on(player, 'pause', this.onPause);
4996
5213
  }
4997
5214
  });
4998
5215
 
@@ -5013,15 +5230,15 @@ vjs.PlayToggle.prototype.onClick = function(){
5013
5230
 
5014
5231
  // OnPlay - Add the vjs-playing class to the element so it can change appearance
5015
5232
  vjs.PlayToggle.prototype.onPlay = function(){
5016
- vjs.removeClass(this.el_, 'vjs-paused');
5017
- vjs.addClass(this.el_, 'vjs-playing');
5233
+ this.removeClass('vjs-paused');
5234
+ this.addClass('vjs-playing');
5018
5235
  this.el_.children[0].children[0].innerHTML = this.localize('Pause'); // change the button text to "Pause"
5019
5236
  };
5020
5237
 
5021
5238
  // OnPause - Add the vjs-paused class to the element so it can change appearance
5022
5239
  vjs.PlayToggle.prototype.onPause = function(){
5023
- vjs.removeClass(this.el_, 'vjs-playing');
5024
- vjs.addClass(this.el_, 'vjs-paused');
5240
+ this.removeClass('vjs-playing');
5241
+ this.addClass('vjs-paused');
5025
5242
  this.el_.children[0].children[0].innerHTML = this.localize('Play'); // change the button text to "Play"
5026
5243
  };
5027
5244
  /**
@@ -5035,7 +5252,7 @@ vjs.CurrentTimeDisplay = vjs.Component.extend({
5035
5252
  init: function(player, options){
5036
5253
  vjs.Component.call(this, player, options);
5037
5254
 
5038
- player.on('timeupdate', vjs.bind(this, this.updateContent));
5255
+ this.on(player, 'timeupdate', this.updateContent);
5039
5256
  }
5040
5257
  });
5041
5258
 
@@ -5076,7 +5293,7 @@ vjs.DurationDisplay = vjs.Component.extend({
5076
5293
  // so the value cannot be written out using this method.
5077
5294
  // Once the order of durationchange and this.player_.duration() being set is figured out,
5078
5295
  // this can be updated.
5079
- player.on('timeupdate', vjs.bind(this, this.updateContent));
5296
+ this.on(player, 'timeupdate', this.updateContent);
5080
5297
  }
5081
5298
  });
5082
5299
 
@@ -5136,7 +5353,7 @@ vjs.RemainingTimeDisplay = vjs.Component.extend({
5136
5353
  init: function(player, options){
5137
5354
  vjs.Component.call(this, player, options);
5138
5355
 
5139
- player.on('timeupdate', vjs.bind(this, this.updateContent));
5356
+ this.on(player, 'timeupdate', this.updateContent);
5140
5357
  }
5141
5358
  });
5142
5359
 
@@ -5235,7 +5452,7 @@ vjs.SeekBar = vjs.Slider.extend({
5235
5452
  /** @constructor */
5236
5453
  init: function(player, options){
5237
5454
  vjs.Slider.call(this, player, options);
5238
- player.on('timeupdate', vjs.bind(this, this.updateARIAAttributes));
5455
+ this.on(player, 'timeupdate', this.updateARIAAttributes);
5239
5456
  player.ready(vjs.bind(this, this.updateARIAAttributes));
5240
5457
  }
5241
5458
  });
@@ -5317,7 +5534,7 @@ vjs.LoadProgressBar = vjs.Component.extend({
5317
5534
  /** @constructor */
5318
5535
  init: function(player, options){
5319
5536
  vjs.Component.call(this, player, options);
5320
- player.on('progress', vjs.bind(this, this.update));
5537
+ this.on(player, 'progress', this.update);
5321
5538
  }
5322
5539
  });
5323
5540
 
@@ -5396,7 +5613,7 @@ vjs.PlayProgressBar.prototype.createEl = function(){
5396
5613
  vjs.SeekHandle = vjs.SliderHandle.extend({
5397
5614
  init: function(player, options) {
5398
5615
  vjs.SliderHandle.call(this, player, options);
5399
- player.on('timeupdate', vjs.bind(this, this.updateContent));
5616
+ this.on(player, 'timeupdate', this.updateContent);
5400
5617
  }
5401
5618
  });
5402
5619
 
@@ -5436,13 +5653,13 @@ vjs.VolumeControl = vjs.Component.extend({
5436
5653
  if (player.tech && player.tech['featuresVolumeControl'] === false) {
5437
5654
  this.addClass('vjs-hidden');
5438
5655
  }
5439
- player.on('loadstart', vjs.bind(this, function(){
5656
+ this.on(player, 'loadstart', function(){
5440
5657
  if (player.tech['featuresVolumeControl'] === false) {
5441
5658
  this.addClass('vjs-hidden');
5442
5659
  } else {
5443
5660
  this.removeClass('vjs-hidden');
5444
5661
  }
5445
- }));
5662
+ });
5446
5663
  }
5447
5664
  });
5448
5665
 
@@ -5469,7 +5686,7 @@ vjs.VolumeBar = vjs.Slider.extend({
5469
5686
  /** @constructor */
5470
5687
  init: function(player, options){
5471
5688
  vjs.Slider.call(this, player, options);
5472
- player.on('volumechange', vjs.bind(this, this.updateARIAAttributes));
5689
+ this.on(player, 'volumechange', this.updateARIAAttributes);
5473
5690
  player.ready(vjs.bind(this, this.updateARIAAttributes));
5474
5691
  }
5475
5692
  });
@@ -5572,19 +5789,20 @@ vjs.MuteToggle = vjs.Button.extend({
5572
5789
  init: function(player, options){
5573
5790
  vjs.Button.call(this, player, options);
5574
5791
 
5575
- player.on('volumechange', vjs.bind(this, this.update));
5792
+ this.on(player, 'volumechange', this.update);
5576
5793
 
5577
5794
  // hide mute toggle if the current tech doesn't support volume control
5578
5795
  if (player.tech && player.tech['featuresVolumeControl'] === false) {
5579
5796
  this.addClass('vjs-hidden');
5580
5797
  }
5581
- player.on('loadstart', vjs.bind(this, function(){
5798
+
5799
+ this.on(player, 'loadstart', function(){
5582
5800
  if (player.tech['featuresVolumeControl'] === false) {
5583
5801
  this.addClass('vjs-hidden');
5584
5802
  } else {
5585
5803
  this.removeClass('vjs-hidden');
5586
5804
  }
5587
- }));
5805
+ });
5588
5806
  }
5589
5807
  });
5590
5808
 
@@ -5640,19 +5858,19 @@ vjs.VolumeMenuButton = vjs.MenuButton.extend({
5640
5858
  vjs.MenuButton.call(this, player, options);
5641
5859
 
5642
5860
  // Same listeners as MuteToggle
5643
- player.on('volumechange', vjs.bind(this, this.update));
5861
+ this.on(player, 'volumechange', this.update);
5644
5862
 
5645
5863
  // hide mute toggle if the current tech doesn't support volume control
5646
5864
  if (player.tech && player.tech['featuresVolumeControl'] === false) {
5647
5865
  this.addClass('vjs-hidden');
5648
5866
  }
5649
- player.on('loadstart', vjs.bind(this, function(){
5867
+ this.on(player, 'loadstart', function(){
5650
5868
  if (player.tech['featuresVolumeControl'] === false) {
5651
5869
  this.addClass('vjs-hidden');
5652
5870
  } else {
5653
5871
  this.removeClass('vjs-hidden');
5654
5872
  }
5655
- }));
5873
+ });
5656
5874
  this.addClass('vjs-menu-button');
5657
5875
  }
5658
5876
  });
@@ -5661,7 +5879,7 @@ vjs.VolumeMenuButton.prototype.createMenu = function(){
5661
5879
  var menu = new vjs.Menu(this.player_, {
5662
5880
  contentElType: 'div'
5663
5881
  });
5664
- var vc = new vjs.VolumeBar(this.player_, vjs.obj.merge({'vertical': true}, this.options_.volumeBar));
5882
+ var vc = new vjs.VolumeBar(this.player_, this.options_.volumeBar);
5665
5883
  vc.on('focus', function() {
5666
5884
  menu.lockShowing();
5667
5885
  });
@@ -5699,8 +5917,8 @@ vjs.PlaybackRateMenuButton = vjs.MenuButton.extend({
5699
5917
  this.updateVisibility();
5700
5918
  this.updateLabel();
5701
5919
 
5702
- player.on('loadstart', vjs.bind(this, this.updateVisibility));
5703
- player.on('ratechange', vjs.bind(this, this.updateLabel));
5920
+ this.on(player, 'loadstart', this.updateVisibility);
5921
+ this.on(player, 'ratechange', this.updateLabel);
5704
5922
  }
5705
5923
  });
5706
5924
 
@@ -5801,7 +6019,7 @@ vjs.PlaybackRateMenuItem = vjs.MenuItem.extend({
5801
6019
  options['selected'] = rate === 1;
5802
6020
  vjs.MenuItem.call(this, player, options);
5803
6021
 
5804
- this.player().on('ratechange', vjs.bind(this, this.update));
6022
+ this.on(player, 'ratechange', this.update);
5805
6023
  }
5806
6024
  });
5807
6025
 
@@ -5827,27 +6045,23 @@ vjs.PosterImage = vjs.Button.extend({
5827
6045
  init: function(player, options){
5828
6046
  vjs.Button.call(this, player, options);
5829
6047
 
5830
- if (player.poster()) {
5831
- this.src(player.poster());
5832
- }
5833
-
5834
- if (!player.poster() || !player.controls()) {
5835
- this.hide();
5836
- }
5837
-
5838
- player.on('posterchange', vjs.bind(this, function(){
5839
- this.src(player.poster());
5840
- }));
5841
-
5842
- if (!player.isAudio()) {
5843
- player.on('play', vjs.bind(this, this.hide));
5844
- }
6048
+ this.update();
6049
+ player.on('posterchange', vjs.bind(this, this.update));
5845
6050
  }
5846
6051
  });
5847
6052
 
5848
- // use the test el to check for backgroundSize style support
5849
- var _backgroundSizeSupported = 'backgroundSize' in vjs.TEST_VID.style;
6053
+ /**
6054
+ * Clean up the poster image
6055
+ */
6056
+ vjs.PosterImage.prototype.dispose = function(){
6057
+ this.player().off('posterchange', this.update);
6058
+ vjs.Button.prototype.dispose.call(this);
6059
+ };
5850
6060
 
6061
+ /**
6062
+ * Create the poster image element
6063
+ * @return {Element}
6064
+ */
5851
6065
  vjs.PosterImage.prototype.createEl = function(){
5852
6066
  var el = vjs.createEl('div', {
5853
6067
  className: 'vjs-poster',
@@ -5856,42 +6070,65 @@ vjs.PosterImage.prototype.createEl = function(){
5856
6070
  tabIndex: -1
5857
6071
  });
5858
6072
 
5859
- if (!_backgroundSizeSupported) {
5860
- // setup an img element as a fallback for IE8
5861
- el.appendChild(vjs.createEl('img'));
6073
+ // To ensure the poster image resizes while maintaining its original aspect
6074
+ // ratio, use a div with `background-size` when available. For browsers that
6075
+ // do not support `background-size` (e.g. IE8), fall back on using a regular
6076
+ // img element.
6077
+ if (!vjs.BACKGROUND_SIZE_SUPPORTED) {
6078
+ this.fallbackImg_ = vjs.createEl('img');
6079
+ el.appendChild(this.fallbackImg_);
5862
6080
  }
5863
6081
 
5864
6082
  return el;
5865
6083
  };
5866
6084
 
5867
- vjs.PosterImage.prototype.src = function(url){
5868
- var el = this.el();
6085
+ /**
6086
+ * Event handler for updates to the player's poster source
6087
+ */
6088
+ vjs.PosterImage.prototype.update = function(){
6089
+ var url = this.player().poster();
6090
+
6091
+ this.setSrc(url);
5869
6092
 
5870
- // getter
5871
- // can't think of a need for a getter here
5872
- // see #838 if on is needed in the future
5873
- // still don't want a getter to set src as undefined
5874
- if (url === undefined) {
5875
- return;
6093
+ // If there's no poster source we should display:none on this component
6094
+ // so it's not still clickable or right-clickable
6095
+ if (url) {
6096
+ // Remove the display style property that hide() adds
6097
+ // as opposed to show() which sets display to block
6098
+ // In the future it might be worth creating an `unhide` component method
6099
+ this.el_.style.display = '';
6100
+ } else {
6101
+ this.hide();
5876
6102
  }
6103
+ };
5877
6104
 
5878
- // setter
5879
- // To ensure the poster image resizes while maintaining its original aspect
5880
- // ratio, use a div with `background-size` when available. For browsers that
5881
- // do not support `background-size` (e.g. IE8), fall back on using a regular
5882
- // img element.
5883
- if (_backgroundSizeSupported) {
5884
- el.style.backgroundImage = 'url("' + url + '")';
6105
+ /**
6106
+ * Set the poster source depending on the display method
6107
+ */
6108
+ vjs.PosterImage.prototype.setSrc = function(url){
6109
+ var backgroundImage;
6110
+
6111
+ if (this.fallbackImg_) {
6112
+ this.fallbackImg_.src = url;
5885
6113
  } else {
5886
- el.firstChild.src = url;
6114
+ backgroundImage = '';
6115
+ // Any falsey values should stay as an empty string, otherwise
6116
+ // this will throw an extra error
6117
+ if (url) {
6118
+ backgroundImage = 'url("' + url + '")';
6119
+ }
6120
+
6121
+ this.el_.style.backgroundImage = backgroundImage;
5887
6122
  }
5888
6123
  };
5889
6124
 
6125
+ /**
6126
+ * Event handler for clicks on the poster image
6127
+ */
5890
6128
  vjs.PosterImage.prototype.onClick = function(){
5891
- // Only accept clicks when controls are enabled
5892
- if (this.player().controls()) {
5893
- this.player_.play();
5894
- }
6129
+ // We don't want a click to trigger playback when controls are disabled
6130
+ // but CSS should be hiding the poster to prevent that from happening
6131
+ this.player_.play();
5895
6132
  };
5896
6133
  /* Loading Spinner
5897
6134
  ================================================================================ */
@@ -5968,7 +6205,7 @@ vjs.ErrorDisplay = vjs.Component.extend({
5968
6205
  vjs.Component.call(this, player, options);
5969
6206
 
5970
6207
  this.update();
5971
- player.on('error', vjs.bind(this, this.update));
6208
+ this.on(player, 'error', this.update);
5972
6209
  }
5973
6210
  });
5974
6211
 
@@ -6043,24 +6280,21 @@ vjs.MediaTechController = vjs.Component.extend({
6043
6280
  * any controls will still keep the user active
6044
6281
  */
6045
6282
  vjs.MediaTechController.prototype.initControlsListeners = function(){
6046
- var player, tech, activateControls, deactivateControls;
6283
+ var player, activateControls;
6047
6284
 
6048
- tech = this;
6049
6285
  player = this.player();
6050
6286
 
6051
- var activateControls = function(){
6287
+ activateControls = function(){
6052
6288
  if (player.controls() && !player.usingNativeControls()) {
6053
- tech.addControlsListeners();
6289
+ this.addControlsListeners();
6054
6290
  }
6055
6291
  };
6056
6292
 
6057
- deactivateControls = vjs.bind(tech, tech.removeControlsListeners);
6058
-
6059
6293
  // Set up event listeners once the tech is ready and has an element to apply
6060
6294
  // listeners to
6061
6295
  this.ready(activateControls);
6062
- player.on('controlsenabled', activateControls);
6063
- player.on('controlsdisabled', deactivateControls);
6296
+ this.on(player, 'controlsenabled', activateControls);
6297
+ this.on(player, 'controlsdisabled', this.removeControlsListeners);
6064
6298
 
6065
6299
  // if we're loading the playback object after it has started loading or playing the
6066
6300
  // video (often with autoplay on) then the loadstart event has already fired and we
@@ -6191,10 +6425,12 @@ vjs.MediaTechController.prototype.stopTrackingProgress = function(){ clearInterv
6191
6425
 
6192
6426
  /*! Time Tracking -------------------------------------------------------------- */
6193
6427
  vjs.MediaTechController.prototype.manualTimeUpdatesOn = function(){
6428
+ var player = this.player_;
6429
+
6194
6430
  this.manualTimeUpdates = true;
6195
6431
 
6196
- this.player().on('play', vjs.bind(this, this.trackCurrentTime));
6197
- this.player().on('pause', vjs.bind(this, this.stopTrackingCurrentTime));
6432
+ this.on(player, 'play', this.trackCurrentTime);
6433
+ this.on(player, 'pause', this.stopTrackingCurrentTime);
6198
6434
  // timeupdate is also called by .currentTime whenever current time is set
6199
6435
 
6200
6436
  // Watch for native timeupdate event
@@ -6297,8 +6533,11 @@ vjs.Html5 = vjs.MediaTechController.extend({
6297
6533
 
6298
6534
  var source = options['source'];
6299
6535
 
6300
- // set the source if one was provided
6301
- if (source && this.el_.currentSrc !== source.src) {
6536
+ // Set the source if one is provided
6537
+ // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
6538
+ // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
6539
+ // anyway so the error gets fired.
6540
+ if (source && (this.el_.currentSrc !== source.src) || (player.tag && player.tag.initNetworkState_ === 3)) {
6302
6541
  this.el_.src = source.src;
6303
6542
  }
6304
6543
 
@@ -6306,7 +6545,8 @@ vjs.Html5 = vjs.MediaTechController.extend({
6306
6545
  // Our goal should be to get the custom controls on mobile solid everywhere
6307
6546
  // so we can remove this all together. Right now this will block custom
6308
6547
  // controls on touch enabled laptops like the Chrome Pixel
6309
- if (vjs.TOUCH_ENABLED && player.options()['nativeControlsForTouch'] !== false) {
6548
+ if (vjs.TOUCH_ENABLED && player.options()['nativeControlsForTouch'] === true) {
6549
+ alert('useNativeControls');
6310
6550
  this.useNativeControls();
6311
6551
  }
6312
6552
 
@@ -6383,7 +6623,7 @@ vjs.Html5.prototype.createEl = function(){
6383
6623
  // Triggers removed using this.off when disposed
6384
6624
  vjs.Html5.prototype.setupTriggers = function(){
6385
6625
  for (var i = vjs.Html5.Events.length - 1; i >= 0; i--) {
6386
- vjs.on(this.el_, vjs.Html5.Events[i], vjs.bind(this, this.eventHandler));
6626
+ this.on(vjs.Html5.Events[i], this.eventHandler);
6387
6627
  }
6388
6628
  };
6389
6629
 
@@ -6476,16 +6716,16 @@ vjs.Html5.prototype.enterFullScreen = function(){
6476
6716
  var video = this.el_;
6477
6717
 
6478
6718
  if ('webkitDisplayingFullscreen' in video) {
6479
- this.one('webkitbeginfullscreen', vjs.bind(this, function(e) {
6719
+ this.one('webkitbeginfullscreen', function() {
6480
6720
  this.player_.isFullscreen(true);
6481
6721
 
6482
- this.one('webkitendfullscreen', vjs.bind(this, function(e) {
6722
+ this.one('webkitendfullscreen', function() {
6483
6723
  this.player_.isFullscreen(false);
6484
6724
  this.player_.trigger('fullscreenchange');
6485
- }));
6725
+ });
6486
6726
 
6487
6727
  this.player_.trigger('fullscreenchange');
6488
- }));
6728
+ });
6489
6729
  }
6490
6730
 
6491
6731
  if (video.paused && video.networkState <= video.HAVE_METADATA) {
@@ -6754,10 +6994,10 @@ vjs.Flash = vjs.MediaTechController.extend({
6754
6994
  // bugzilla bug: https://bugzilla.mozilla.org/show_bug.cgi?id=836786
6755
6995
  if (vjs.IS_FIREFOX) {
6756
6996
  this.ready(function(){
6757
- vjs.on(this.el(), 'mousemove', vjs.bind(this, function(){
6997
+ this.on('mousemove', function(){
6758
6998
  // since it's a custom event, don't bubble higher than the player
6759
6999
  this.player().trigger({ 'type':'mousemove', 'bubbles': false });
6760
- }));
7000
+ });
6761
7001
  });
6762
7002
  }
6763
7003
 
@@ -7876,7 +8116,7 @@ vjs.TextTrackMenuItem = vjs.MenuItem.extend({
7876
8116
  options['selected'] = track.dflt();
7877
8117
  vjs.MenuItem.call(this, player, options);
7878
8118
 
7879
- this.player_.on(track.kind() + 'trackchange', vjs.bind(this, this.update));
8119
+ this.on(player, track.kind() + 'trackchange', this.update);
7880
8120
  }
7881
8121
  });
7882
8122
 
@@ -8178,7 +8418,7 @@ vjs.obj.merge(vjs.ControlBar.prototype.options_['children'], {
8178
8418
  */
8179
8419
  vjs.JSON;
8180
8420
 
8181
- if (typeof window.JSON !== 'undefined' && window.JSON.parse === 'function') {
8421
+ if (typeof window.JSON !== 'undefined' && typeof window.JSON.parse === 'function') {
8182
8422
  vjs.JSON = window.JSON;
8183
8423
 
8184
8424
  } else {
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  Video.js Default Styles (http://videojs.com)
3
- Version 4.9.1
3
+ Version 4.10.0
4
4
  Create your own skin at http://designer.videojs.com
5
5
  */
6
6
  /* SKIN
@@ -880,6 +880,19 @@ body.vjs-full-window {
880
880
  padding: 0;
881
881
  width: 100%;
882
882
  }
883
+ /* Hide the poster after the video has started playing */
884
+ .video-js.vjs-has-started .vjs-poster {
885
+ display: none;
886
+ }
887
+ /* Don't hide the poster if we're playing audio */
888
+ .video-js.vjs-audio.vjs-has-started .vjs-poster {
889
+ display: block;
890
+ }
891
+ /* Hide the poster when controls are disabled because it's clickable
892
+ and the native poster can take over */
893
+ .video-js.vjs-controls-disabled .vjs-poster {
894
+ display: none;
895
+ }
883
896
  /* Hide the poster when native controls are used otherwise it covers them */
884
897
  .video-js.vjs-using-native-controls .vjs-poster {
885
898
  display: none;
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: videojs_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.9.1
4
+ version: 4.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Behan