Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(WIP) feat: Allow picture sources to be set on poster #8052

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions sandbox/poster.html.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Video.js Sandbox</title>
<link href="../dist/video-js.css" rel="stylesheet" type="text/css">
<script src="../dist/video.js"></script>
</head>
<body>
<div style="background-color:#eee; border: 1px solid #777; padding: 10px; margin-bottom: 20px; font-size: .8em; line-height: 1.5em; font-family: Verdana, sans-serif;">
<p>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</p>
<pre>npm start</pre>
<pre>open http://localhost:9999/sandbox/index.html</pre>
</div>

<video-js
id="vid1"
controls
preload="auto"
width="640"
height="264">
<source src="https://vjs.zencdn.net/v/oceans.mp4" type="video/mp4">
<source src="https://vjs.zencdn.net/v/oceans.webm" type="video/webm">
<source src="https://vjs.zencdn.net/v/oceans.ogv" type="video/ogg">
<track kind="captions" src="../docs/examples/shared/example-captions.vtt" srclang="en" label="English">
<p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="https://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a></p>
</video-js>

<script>
var vid = document.getElementById('vid1');
var player = videojs(vid, {
posterOptions: {
sources: [{
type: 'image/webp',
srcset: 'https://www.gstatic.com/webp/gallery/1.webp'
},
{
type: 'image/png',
srcset: 'https://vjs.zencdn.net/v/oceans.png'
}],
alt: 'This would be descriptive text for a screen reader, if appropriate for the image.'
}
});
player.log('window.player created', player);
</script>

</body>
</html>
81 changes: 68 additions & 13 deletions src/js/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 <source> 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;

Expand All @@ -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
Expand Down
31 changes: 26 additions & 5 deletions src/js/poster-image.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'.
Expand Down Expand Up @@ -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();
Expand All @@ -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 <picture> <source>s.
* @param {string} [opts.alt]
* Alt text to set on the <picture>'s <img>..
*/
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);
});
}
}

/**
Expand Down