diff --git a/sandbox/poster.html.example b/sandbox/poster.html.example new file mode 100644 index 0000000000..9511ce4091 --- /dev/null +++ b/sandbox/poster.html.example @@ -0,0 +1,48 @@ + + + + + Video.js Sandbox + + + + +
+

You can use /sandbox/ for writing and testing your own code. Nothing in /sandbox/ will get checked into the repo, except files that end in .example (so don't edit or add those files). To get started run `npm start` and open the index.html

+
npm start
+
open http://localhost:9999/sandbox/index.html
+
+ + + + + + +

To view this video please enable JavaScript, and consider upgrading to a web browser that supports HTML5 video

+
+ + + + + diff --git a/src/js/player.js b/src/js/player.js index e8e5b30c4d..afbaadfd1a 100644 --- a/src/js/player.js +++ b/src/js/player.js @@ -431,6 +431,9 @@ class Player extends Component { // Set poster this.poster_ = options.poster || ''; + if (options.posterOptions) { + this.posterOpts_ = this.sanitisePosterOpts_(options.posterOptions); + } // Set controls this.controls_ = !!options.controls; @@ -3714,32 +3717,45 @@ class Player extends Component { * * @fires Player#posterchange * - * @param {string} [src] + * @param {string | object} [opts] + * Poster image source URL, or object of settings + * @param {string} [opts.img] * Poster image source URL + * @param {string} [opts.alt] + * Poster image alt text. Set to null by default, but add text if there is content + * in the poster image that should be read by a screen reader + * @param {Array} [opts.sources] + * Array of objects repreesenting element attributes * * @return {string} * The current value of poster when getting */ - poster(src) { - if (src === undefined) { - return this.poster_; + poster(opts) { + // To be non-breaking, the getter returns a string. However the string will be + // the actual source chosen by the browser. In the case only a string is set, there + // is no change in behaviour. + if (opts === undefined) { + return this.posterImage.$('img').currentSrc || 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 = ''; - } + opts = this.sanitisePosterOpts_(opts); - if (src === this.poster_) { + // No change + if (opts === this.posterOpts_) { return; } - // update the internal poster variable - this.poster_ = src; + // update the internal poster variables + this.poster_ = opts.img; + this.posterOpts_ = opts; // update the tech's poster - this.techCall_('setPoster', src); + // Don't set a poster if desirable to not have mis-matching posters on the tech and PosterImage + if (this.options_.noTechPoster || (opts && opts.sources && opts.sources.length > 0)) { + this.techCall_('setPoster', ''); + } else { + this.techCall_('setPoster', opts.img); + } this.isPosterFromTech_ = false; @@ -3753,6 +3769,45 @@ class Player extends Component { this.trigger('posterchange'); } + /** + * Get poster options. Unlike similar methods this is not also a setter, as + * options should be set with `poster()` + * + * @return {Object} + */ + posterOpts() { + if (arguments) { + this.log.warn('`posterOpts()` is not a setter'); + } + return this.posterOpts_; + } + + /** + * Ensures poster options include a img + * + * @param {Object} opts + * @return {Object} opts + */ + sanitisePosterOpts_(opts) { + if (!opts || typeof opts !== 'object') { + opts = { + img: opts + }; + } + + // If img is missing but have sources, use a source as the img + // If no img and no sources, make img an empty string so `poster(null)` works as before + if (!opts.img) { + if (opts.sources && opts.sources.length) { + opts.img = opts.sources[opts.sources.length - 1].srcset.split(',')[0]; + } else { + opts.img = ''; + } + } + + return opts; + } + /** * Some techs (e.g. YouTube) can provide a poster source in an * asynchronous way. We want the poster component to use this diff --git a/src/js/poster-image.js b/src/js/poster-image.js index ad890323d0..57cba80617 100644 --- a/src/js/poster-image.js +++ b/src/js/poster-image.js @@ -62,6 +62,7 @@ class PosterImage extends ClickableComponent { /** * Get or set the `PosterImage`'s crossOrigin option. * + * * @param {string|null} [value] * The value to set the crossOrigin to. If an argument is * given, must be one of `'anonymous'` or `'use-credentials'`, or 'null'. @@ -105,13 +106,14 @@ class PosterImage extends ClickableComponent { * The `Player#posterchange` event that triggered this function. */ update(event) { - const url = this.player().poster(); + const opts = this.player().posterOpts_; - this.setSrc(url); + this.setSrc(opts); // If there's no poster source we should display:none on this component // so it's not still clickable or right-clickable - if (url) { + // TODO: is this catching all unset scenarios + if (opts.img) { this.show(); } else { this.hide(); @@ -123,9 +125,28 @@ class PosterImage extends ClickableComponent { * * @param {string} url * The URL to the source for the `PosterImage`. + * @param {Object} [opts] + * Image options + * @param {Object} [opts.sources] + * An array of attributes to construct s. + * @param {string} [opts.alt] + * Alt text to set on the 's .. */ - setSrc(url) { - this.el_.querySelector('img').src = url; + setSrc(opts) { + const imgEl = this.$('img'); + + imgEl.src = opts.img; + imgEl.setAttribute('alt', opts.alt || ''); + + this.$$('source').forEach(s => { + this.el_.removeChild(s); + }); + + if (opts.sources) { + opts.sources.forEach(s => { + this.el_.insertBefore(Dom.createEl('source', {}, s), imgEl); + }); + } } /**