From 6c78f51487708c4f09377b790ac65662ae6a52b1 Mon Sep 17 00:00:00 2001 From: David LaPalomento Date: Sat, 24 Aug 2013 19:34:26 -0400 Subject: [PATCH 1/7] Allow poster to be changed after init. Fixes #525 When poster() is called with a URL, fire a `posterchange` event to update the PosterImage source and update the video element attribute. --- src/js/media/flash.js | 3 +++ src/js/media/html5.js | 3 +++ src/js/player.js | 17 +++++++++----- src/js/poster.js | 49 ++++++++++++++++++++++++++++++++--------- test/unit/controls.js | 29 ++++++++++++++++++++++++ test/unit/mediafaker.js | 4 ++++ test/unit/player.js | 22 ++++++++++++++++++ 7 files changed, 111 insertions(+), 16 deletions(-) diff --git a/src/js/media/flash.js b/src/js/media/flash.js index 5ad6ca18fe..95780d63e2 100644 --- a/src/js/media/flash.js +++ b/src/js/media/flash.js @@ -272,6 +272,9 @@ vjs.Flash.prototype.load = function(){ vjs.Flash.prototype.poster = function(){ this.el_.vjs_getProperty('poster'); }; +vjs.Flash.prototype.setPoster = function(){ + // poster images are not handled by the Flash tech so make this a no-op +}; vjs.Flash.prototype.buffered = function(){ return vjs.createTimeRange(0, this.el_.vjs_getProperty('buffered')); diff --git a/src/js/media/html5.js b/src/js/media/html5.js index 74f3858a00..6f1a522fac 100644 --- a/src/js/media/html5.js +++ b/src/js/media/html5.js @@ -214,6 +214,9 @@ vjs.Html5.prototype.src = function(src){ this.el_.src = src; }; vjs.Html5.prototype.load = function(){ this.el_.load(); }; vjs.Html5.prototype.currentSrc = function(){ return this.el_.currentSrc; }; +vjs.Html5.prototype.poster = function(){ return this.el_.poster; }; +vjs.Html5.prototype.setPoster = function(val){ this.el_.poster = val; }; + vjs.Html5.prototype.preload = function(){ return this.el_.preload; }; vjs.Html5.prototype.setPreload = function(val){ this.el_.preload = val; }; diff --git a/src/js/player.js b/src/js/player.js index f160e31e46..c8aa1896ca 100644 --- a/src/js/player.js +++ b/src/js/player.js @@ -1093,11 +1093,18 @@ vjs.Player.prototype.poster_; * @return {vjs.Player} self when setting */ vjs.Player.prototype.poster = function(src){ - if (src !== undefined) { - this.poster_ = src; - return this; + if (src === undefined) { + return this.poster_; } - return this.poster_; + + // update the internal poster variable + this.poster_ = src; + + // alert components that the poster has been set + this.trigger('posterchange'); + + // update the tech's poster + this.techCall('setPoster', src); }; /** @@ -1381,5 +1388,3 @@ vjs.Player.prototype.listenForUserActivity = function(){ } })(); - - diff --git a/src/js/poster.js b/src/js/poster.js index 24cb8d3d68..bb19b382f5 100644 --- a/src/js/poster.js +++ b/src/js/poster.js @@ -12,32 +12,61 @@ vjs.PosterImage = vjs.Button.extend({ init: function(player, options){ vjs.Button.call(this, player, options); + if (player.poster()) { + this.src(player.poster()); + } + if (!player.poster() || !player.controls()) { this.hide(); } + player.on('posterchange', vjs.bind(this, function(){ + this.src(player.poster()); + })); + player.on('play', vjs.bind(this, this.hide)); } }); vjs.PosterImage.prototype.createEl = function(){ var el = vjs.createEl('div', { - className: 'vjs-poster', + className: 'vjs-poster', + + // Don't want poster to be tabbable. + tabIndex: -1 + }), + poster = this.player_.poster(); + + return el; +}; - // Don't want poster to be tabbable. - tabIndex: -1 - }), - poster = this.player_.poster(); +vjs.PosterImage.prototype.src = function(url){ + var el = this.el(); - if (poster) { + // getter + if (url === undefined) { if ('backgroundSize' in el.style) { - el.style.backgroundImage = 'url("' + poster + '")'; - } else { - el.appendChild(vjs.createEl('img', { src: poster })); + if (el.style.backgroundImage) { + // parse the poster url from the background-image value + return (/url\(['"]?(.*)['"]?\)/).exec(el.style.backgroundImage)[1]; + } + + // the poster is not specified + return ''; } + return el.querySelector('img').src; } - return el; + // setter + // 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 ('backgroundSize' in el.style) { + el.style.backgroundImage = 'url("' + url + '")'; + } else { + el.appendChild(vjs.createEl('img', { src: url })); + } }; vjs.PosterImage.prototype.onClick = function(){ diff --git a/test/unit/controls.js b/test/unit/controls.js index 30cdd95c23..dd0199a5f9 100644 --- a/test/unit/controls.js +++ b/test/unit/controls.js @@ -100,3 +100,32 @@ test('calculateDistance should use changedTouches, if available', function() { equal(slider.calculateDistance(event), 0.5, 'we should have touched exactly in the center, so, the ratio should be half'); }); + +test('the poster getter should work correctly even when background-size is not available', function() { + var noop = function(){}, + url = 'http://example.com/poster.jpg', + player = { + controls: noop, + id: noop, + on: noop, + ready: noop, + poster: function(){ + return url; + } + }, + poster = new vjs.PosterImage(player); + + // mock out el() to return an element that behaves like IE8 + poster.el = function(){ + return { + style: {}, + querySelector: function() { + return { + src: url + }; + } + }; + }; + + equal(url, poster.src(), 'the poster url is returned'); +}); diff --git a/test/unit/mediafaker.js b/test/unit/mediafaker.js index 108f9c6ff9..a5a9e08041 100644 --- a/test/unit/mediafaker.js +++ b/test/unit/mediafaker.js @@ -30,6 +30,10 @@ vjs.MediaFaker.prototype.createEl = function(){ return el; }; +// fake a poster attribute to mimic the video element +vjs.MediaFaker.prototype.poster = function(){ return this.el().poster; }; +vjs.MediaFaker.prototype['setPoster'] = function(val){ this.el().poster = val; }; + vjs.MediaFaker.prototype.currentTime = function(){ return 0; }; vjs.MediaFaker.prototype.seeking = function(){ return false; }; vjs.MediaFaker.prototype.volume = function(){ return 0; }; diff --git a/test/unit/player.js b/test/unit/player.js index 497a98f9db..26bc046127 100644 --- a/test/unit/player.js +++ b/test/unit/player.js @@ -176,6 +176,28 @@ test('should transfer the poster attribute unmodified', function(){ player.dispose(); }); +test('should allow the poster to be changed after init', function() { + var tag, fixture, updatedPoster, player; + tag = PlayerTest.makeTag(); + tag.setAttribute('poster', 'http://example.com/poster.jpg'); + fixture = document.getElementById('qunit-fixture'); + + fixture.appendChild(tag); + player = new vjs.Player(tag, { + 'techOrder': ['mediaFaker'] + }); + + updatedPoster = 'http://example.com/udpdated-poster.jpg'; + player.poster(updatedPoster); + strictEqual(player.poster(), updatedPoster, 'the updated poster is returned'); + strictEqual(player.tech.el().poster, updatedPoster, 'the poster attribute is updated'); + strictEqual(fixture.querySelector('.vjs-poster').style.backgroundImage, + 'url(' + updatedPoster + ')', + 'the poster div background is updated'); + + player.dispose(); +}); + test('should load a media controller', function(){ var player = PlayerTest.makePlayer({ preload: 'none', From 1339ddeb4ef7a349b480b21ce41376415cdd0804 Mon Sep 17 00:00:00 2001 From: David LaPalomento Date: Thu, 17 Oct 2013 13:09:39 -0400 Subject: [PATCH 2/7] Trigger posterchange after updating the tech Wait until the tech has updated its poster image before alerting components so they don't see the intermediate state in event handlers. Remove unused variable from PosterImage.createEl. --- src/js/player.js | 6 +++--- src/js/poster.js | 7 ++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/js/player.js b/src/js/player.js index c8aa1896ca..d2bc47f1f0 100644 --- a/src/js/player.js +++ b/src/js/player.js @@ -1100,11 +1100,11 @@ vjs.Player.prototype.poster = function(src){ // update the internal poster variable this.poster_ = src; - // alert components that the poster has been set - this.trigger('posterchange'); - // update the tech's poster this.techCall('setPoster', src); + + // alert components that the poster has been set + this.trigger('posterchange'); }; /** diff --git a/src/js/poster.js b/src/js/poster.js index bb19b382f5..7c53677612 100644 --- a/src/js/poster.js +++ b/src/js/poster.js @@ -29,15 +29,12 @@ vjs.PosterImage = vjs.Button.extend({ }); vjs.PosterImage.prototype.createEl = function(){ - var el = vjs.createEl('div', { + return vjs.createEl('div', { className: 'vjs-poster', // Don't want poster to be tabbable. tabIndex: -1 - }), - poster = this.player_.poster(); - - return el; + }); }; vjs.PosterImage.prototype.src = function(url){ From 2c33552a1a190144fbe192d6b8198dde0aa420a2 Mon Sep 17 00:00:00 2001 From: David LaPalomento Date: Mon, 11 Nov 2013 17:52:04 -0500 Subject: [PATCH 3/7] Don't create new img elements each time the poster is set on ie8 Create the img fallback for the poster during PosterImage initialization on ie8 so we can avoid having to check and possibly create it each time the src is set. Add a test to ensure that new elements are not appended to the poster component when the img fallback is in use and the src attribute is modified. --- src/js/poster.js | 13 ++++++++++--- test/unit/controls.js | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/js/poster.js b/src/js/poster.js index 7c53677612..00e0c6e235 100644 --- a/src/js/poster.js +++ b/src/js/poster.js @@ -29,16 +29,23 @@ vjs.PosterImage = vjs.Button.extend({ }); vjs.PosterImage.prototype.createEl = function(){ - return vjs.createEl('div', { + var el = vjs.createEl('div', { className: 'vjs-poster', // Don't want poster to be tabbable. tabIndex: -1 }); + + if (!('backgroundSize' in el.style)) { + // setup an img element as a fallback for IE8 + el.appendChild(vjs.createEl('img')); + } + + return el; }; vjs.PosterImage.prototype.src = function(url){ - var el = this.el(); + var el = this.el(), imgFallback; // getter if (url === undefined) { @@ -62,7 +69,7 @@ vjs.PosterImage.prototype.src = function(url){ if ('backgroundSize' in el.style) { el.style.backgroundImage = 'url("' + url + '")'; } else { - el.appendChild(vjs.createEl('img', { src: url })); + el.querySelector('img').src = url; } }; diff --git a/test/unit/controls.js b/test/unit/controls.js index dd0199a5f9..ed3bb4e157 100644 --- a/test/unit/controls.js +++ b/test/unit/controls.js @@ -129,3 +129,35 @@ test('the poster getter should work correctly even when background-size is not a equal(url, poster.src(), 'the poster url is returned'); }); + +test('the poster setter should reuse an img when background-size is not available', function() { + var noop = function(){}, + url = 'http://example.com/poster.jpg', + img = {}, + player = { + controls: noop, + id: noop, + on: noop, + ready: noop, + poster: function(){ + return url; + } + }, + poster = new vjs.PosterImage(player); + + // mock out el() to return an element that behaves like IE8 + poster.el = function(){ + return { + appendChild: function() { + ok(false, 'a new img should not be added to the poster'); + }, + style: {}, + querySelector: function() { + return img; + } + }; + }; + + poster.src(url); + equal(img.src, url, 'the img is reused for the new src'); +}); From 1ea766923b5981505559cbd4e89228157e33691a Mon Sep 17 00:00:00 2001 From: BCJwhisenant Date: Tue, 19 Nov 2013 16:20:56 -0500 Subject: [PATCH 4/7] fixing a broken IE8 test, and adding a negative test, for poster switching. --- test/unit/player.js | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/test/unit/player.js b/test/unit/player.js index 26bc046127..b3275bf23e 100644 --- a/test/unit/player.js +++ b/test/unit/player.js @@ -187,7 +187,7 @@ test('should allow the poster to be changed after init', function() { 'techOrder': ['mediaFaker'] }); - updatedPoster = 'http://example.com/udpdated-poster.jpg'; + updatedPoster = 'http://example.com/updated-poster.jpg'; player.poster(updatedPoster); strictEqual(player.poster(), updatedPoster, 'the updated poster is returned'); strictEqual(player.tech.el().poster, updatedPoster, 'the poster attribute is updated'); @@ -198,6 +198,30 @@ test('should allow the poster to be changed after init', function() { player.dispose(); }); +test('should ignore setting an undefined poster after init', function() { + var tag, fixture, updatedPoster, originalPoster, player; + tag = PlayerTest.makeTag(); + tag.setAttribute('poster', 'http://example.com/poster.jpg'); + fixture = document.getElementById('qunit-fixture'); + + fixture.appendChild(tag); + player = new vjs.Player(tag, { + 'techOrder': ['mediaFaker'] + }); + + originalPoster = player.poster(); + + updatedPoster = undefined; + player.poster(updatedPoster); + strictEqual(player.poster(), originalPoster, 'the original poster is returned'); + strictEqual(player.tech.el().poster, originalPoster, 'the poster attribute is unchanged'); + strictEqual(fixture.querySelector('.vjs-poster').style.backgroundImage, + 'url(' + originalPoster + ')', + 'the poster div background is unchanged'); + + player.dispose(); +}); + test('should load a media controller', function(){ var player = PlayerTest.makePlayer({ preload: 'none', From cede43ea4e5b6560e5e57e9be91f393f265f63b1 Mon Sep 17 00:00:00 2001 From: BCJwhisenant Date: Wed, 20 Nov 2013 11:42:39 -0500 Subject: [PATCH 5/7] modified the poster-switching test to accomodate IE8 added a negative test for an undefined poster --- test/unit/player.js | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/test/unit/player.js b/test/unit/player.js index b3275bf23e..d78f2dd2a4 100644 --- a/test/unit/player.js +++ b/test/unit/player.js @@ -177,7 +177,7 @@ test('should transfer the poster attribute unmodified', function(){ }); test('should allow the poster to be changed after init', function() { - var tag, fixture, updatedPoster, player; + var tag, fixture, updatedPoster, player, ePoster, eImg; tag = PlayerTest.makeTag(); tag.setAttribute('poster', 'http://example.com/poster.jpg'); fixture = document.getElementById('qunit-fixture'); @@ -189,17 +189,31 @@ test('should allow the poster to be changed after init', function() { updatedPoster = 'http://example.com/updated-poster.jpg'; player.poster(updatedPoster); + strictEqual(player.poster(), updatedPoster, 'the updated poster is returned'); strictEqual(player.tech.el().poster, updatedPoster, 'the poster attribute is updated'); - strictEqual(fixture.querySelector('.vjs-poster').style.backgroundImage, - 'url(' + updatedPoster + ')', - 'the poster div background is updated'); + + ePoster = document.querySelector('.vjs-poster'); + ok(ePoster, 'vjs-poster element should exist'); + + if (!('backgroundSize' in ePoster.style)) { + eImg = document.getElementsByTagName('img')[0]; + ok(eImg, 'image element should exist if the poster div has no background-size CSS property'); + var eImgSrc = eImg.getAttribute('src'); + strictEqual(eImgSrc, + updatedPoster, + 'the poster img src is updated'); + } else { + strictEqual(ePoster.style.backgroundImage, + 'url(' + updatedPoster + ')', + 'the poster div background is updated'); + } player.dispose(); }); test('should ignore setting an undefined poster after init', function() { - var tag, fixture, updatedPoster, originalPoster, player; + var tag, fixture, updatedPoster, originalPoster, player, ePoster, eImg; tag = PlayerTest.makeTag(); tag.setAttribute('poster', 'http://example.com/poster.jpg'); fixture = document.getElementById('qunit-fixture'); @@ -215,9 +229,22 @@ test('should ignore setting an undefined poster after init', function() { player.poster(updatedPoster); strictEqual(player.poster(), originalPoster, 'the original poster is returned'); strictEqual(player.tech.el().poster, originalPoster, 'the poster attribute is unchanged'); - strictEqual(fixture.querySelector('.vjs-poster').style.backgroundImage, + + ePoster = document.querySelector('.vjs-poster'); + ok(ePoster, 'vjs-poster element should exist'); + + if (!('backgroundSize' in ePoster.style)) { + eImg = document.getElementsByTagName('img')[0]; + ok(eImg, 'image element should exist if the poster div has no background-size CSS property'); + var eImgSrc = eImg.getAttribute('src'); + strictEqual(eImgSrc, + originalPoster, + 'the poster img src is updated'); + } else { + strictEqual(fixture.querySelector('.vjs-poster').style.backgroundImage, 'url(' + originalPoster + ')', 'the poster div background is unchanged'); + } player.dispose(); }); From 7b9c1b18a9ecab8f753e143a8917978f09b78de2 Mon Sep 17 00:00:00 2001 From: BCJwhisenant Date: Wed, 20 Nov 2013 11:50:51 -0500 Subject: [PATCH 6/7] fixed the assertion message in the 'undefined' image case --- test/unit/player.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/player.js b/test/unit/player.js index d78f2dd2a4..55c2a7e42a 100644 --- a/test/unit/player.js +++ b/test/unit/player.js @@ -239,7 +239,7 @@ test('should ignore setting an undefined poster after init', function() { var eImgSrc = eImg.getAttribute('src'); strictEqual(eImgSrc, originalPoster, - 'the poster img src is updated'); + 'the poster img src is not updated'); } else { strictEqual(fixture.querySelector('.vjs-poster').style.backgroundImage, 'url(' + originalPoster + ')', From 1b2c8c80cd521142bc9f5ae1aba459033580d4d3 Mon Sep 17 00:00:00 2001 From: BCJwhisenant Date: Wed, 20 Nov 2013 14:26:41 -0500 Subject: [PATCH 7/7] fixed test breakage in Firefox and IE10 (quotes were not being handled properly in the test data) testing: ran the tests at the command line, and in Chrome, Firefox, IE8, IE10, Firefox and Safari all passed --- test/unit/player.js | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/test/unit/player.js b/test/unit/player.js index 55c2a7e42a..19f7030b97 100644 --- a/test/unit/player.js +++ b/test/unit/player.js @@ -177,7 +177,7 @@ test('should transfer the poster attribute unmodified', function(){ }); test('should allow the poster to be changed after init', function() { - var tag, fixture, updatedPoster, player, ePoster, eImg; + var tag, fixture, updatedPoster, player, posterElement, posterElementUrl, imageElement; tag = PlayerTest.makeTag(); tag.setAttribute('poster', 'http://example.com/poster.jpg'); fixture = document.getElementById('qunit-fixture'); @@ -193,18 +193,19 @@ test('should allow the poster to be changed after init', function() { strictEqual(player.poster(), updatedPoster, 'the updated poster is returned'); strictEqual(player.tech.el().poster, updatedPoster, 'the poster attribute is updated'); - ePoster = document.querySelector('.vjs-poster'); - ok(ePoster, 'vjs-poster element should exist'); + posterElement = document.querySelector('.vjs-poster'); + ok(posterElement, 'vjs-poster element should exist'); - if (!('backgroundSize' in ePoster.style)) { - eImg = document.getElementsByTagName('img')[0]; - ok(eImg, 'image element should exist if the poster div has no background-size CSS property'); - var eImgSrc = eImg.getAttribute('src'); - strictEqual(eImgSrc, + if (!('backgroundSize' in posterElement.style)) { + imageElement = document.getElementsByTagName('img')[0]; + ok(imageElement, 'image element should exist if the poster div has no background-size CSS property'); + var imageElementSrc = imageElement.getAttribute('src'); + strictEqual(imageElementSrc, updatedPoster, 'the poster img src is updated'); } else { - strictEqual(ePoster.style.backgroundImage, + posterElementUrl = posterElement.style.backgroundImage.replace(/"/g, ''); + strictEqual(posterElementUrl, 'url(' + updatedPoster + ')', 'the poster div background is updated'); } @@ -213,7 +214,7 @@ test('should allow the poster to be changed after init', function() { }); test('should ignore setting an undefined poster after init', function() { - var tag, fixture, updatedPoster, originalPoster, player, ePoster, eImg; + var tag, fixture, updatedPoster, originalPoster, player, posterElement, posterElementUrl, imageElement; tag = PlayerTest.makeTag(); tag.setAttribute('poster', 'http://example.com/poster.jpg'); fixture = document.getElementById('qunit-fixture'); @@ -230,18 +231,19 @@ test('should ignore setting an undefined poster after init', function() { strictEqual(player.poster(), originalPoster, 'the original poster is returned'); strictEqual(player.tech.el().poster, originalPoster, 'the poster attribute is unchanged'); - ePoster = document.querySelector('.vjs-poster'); - ok(ePoster, 'vjs-poster element should exist'); + posterElement = document.querySelector('.vjs-poster'); + ok(posterElement, 'vjs-poster element should exist'); - if (!('backgroundSize' in ePoster.style)) { - eImg = document.getElementsByTagName('img')[0]; - ok(eImg, 'image element should exist if the poster div has no background-size CSS property'); - var eImgSrc = eImg.getAttribute('src'); - strictEqual(eImgSrc, + if (!('backgroundSize' in posterElement.style)) { + imageElement = document.getElementsByTagName('img')[0]; + ok(imageElement, 'image element should exist if the poster div has no background-size CSS property'); + var imageElementSrc = imageElement.getAttribute('src'); + strictEqual(imageElementSrc, originalPoster, 'the poster img src is not updated'); } else { - strictEqual(fixture.querySelector('.vjs-poster').style.backgroundImage, + posterElementUrl = posterElement.style.backgroundImage.replace(/"/g, ''); + strictEqual(posterElementUrl, 'url(' + originalPoster + ')', 'the poster div background is unchanged'); }