diff --git a/src/js/controls.js b/src/js/controls.js index 3f720925..d266ed6b 100644 --- a/src/js/controls.js +++ b/src/js/controls.js @@ -481,6 +481,7 @@ const controls = { // Video playing case 'timeupdate': case 'seeking': + case 'seeked': value = utils.getPercentage(this.currentTime, this.duration); // Set seek range value only if it's a 'natural' time event @@ -601,9 +602,10 @@ const controls = { controls.updateProgress.call(this, event); }, - // Show the duration on metadataloaded + // Show the duration on metadataloaded or durationchange events durationUpdate() { - if (!this.supported.ui) { + // Bail if no ui or durationchange event triggered after playing/seek when invertTime is false + if (!this.supported.ui || (!this.config.invertTime && this.currentTime)) { return; } diff --git a/src/js/listeners.js b/src/js/listeners.js index d5748806..99eeade4 100644 --- a/src/js/listeners.js +++ b/src/js/listeners.js @@ -273,7 +273,7 @@ class Listeners { // Listen for media events media() { // Time change on media - utils.on(this.player.media, 'timeupdate seeking', event => controls.timeUpdate.call(this.player, event)); + utils.on(this.player.media, 'timeupdate seeking seeked', event => controls.timeUpdate.call(this.player, event)); // Display duration utils.on(this.player.media, 'durationchange loadeddata loadedmetadata', event => controls.durationUpdate.call(this.player, event)); @@ -295,7 +295,7 @@ class Listeners { }); // Check for buffer progress - utils.on(this.player.media, 'progress playing', event => controls.updateProgress.call(this.player, event)); + utils.on(this.player.media, 'progress playing seeking seeked', event => controls.updateProgress.call(this.player, event)); // Handle volume changes utils.on(this.player.media, 'volumechange', event => controls.updateVolume.call(this.player, event)); diff --git a/src/js/plugins/vimeo.js b/src/js/plugins/vimeo.js index 96b36781..46d4f3f9 100644 --- a/src/js/plugins/vimeo.js +++ b/src/js/plugins/vimeo.js @@ -7,6 +7,14 @@ import controls from './../controls'; import ui from './../ui'; import utils from './../utils'; +// Set playback state and trigger change (only on actual change) +function assurePlaybackState(play) { + if (this.media.paused === play) { + this.media.paused = !play; + utils.dispatchEvent.call(this, this.media, play ? 'play' : 'pause'); + } +} + const vimeo = { setup() { // Add embed class for responsive @@ -120,15 +128,13 @@ const vimeo = { // Create a faux HTML5 API using the Vimeo API player.media.play = () => { - player.embed.play().then(() => { - player.media.paused = false; - }); + assurePlaybackState.call(player, true); + return player.embed.play(); }; player.media.pause = () => { - player.embed.pause().then(() => { - player.media.paused = true; - }); + assurePlaybackState.call(player, false); + return player.embed.pause(); }; player.media.stop = () => { @@ -143,25 +149,26 @@ const vimeo = { return currentTime; }, set(time) { - // Get current paused state - // Vimeo will automatically play on seek - const { paused } = player.media; + // Vimeo will automatically play on seek if the video hasn't been played before - // Set seeking flag - player.media.seeking = true; + // Get current paused state and volume etc + const { embed, media, paused, volume } = player; - // Trigger seeking - utils.dispatchEvent.call(player, player.media, 'seeking'); + // Set seeking state and trigger event + media.seeking = true; + utils.dispatchEvent.call(player, media, 'seeking'); - // Seek after events - player.embed.setCurrentTime(time).catch(() => { - // Do nothing - }); - - // Restore pause state - if (paused) { - player.pause(); - } + // If paused, mute until seek is complete + Promise.resolve(paused && embed.setVolume(0)) + // Seek + .then(() => embed.setCurrentTime(time)) + // Restore paused + .then(() => paused && embed.pause()) + // Restore volume + .then(() => paused && embed.setVolume(volume)) + .catch(() => { + // Do nothing + }); }, }); @@ -315,17 +322,12 @@ const vimeo = { }); player.embed.on('play', () => { - // Only fire play if paused before - if (player.media.paused) { - utils.dispatchEvent.call(player, player.media, 'play'); - } - player.media.paused = false; + assurePlaybackState.call(player, true); utils.dispatchEvent.call(player, player.media, 'playing'); }); player.embed.on('pause', () => { - player.media.paused = true; - utils.dispatchEvent.call(player, player.media, 'pause'); + assurePlaybackState.call(player, false); }); player.embed.on('timeupdate', data => { @@ -356,7 +358,6 @@ const vimeo = { player.embed.on('seeked', () => { player.media.seeking = false; utils.dispatchEvent.call(player, player.media, 'seeked'); - utils.dispatchEvent.call(player, player.media, 'play'); }); player.embed.on('ended', () => { diff --git a/src/js/plugins/youtube.js b/src/js/plugins/youtube.js index 10283998..67b8093e 100644 --- a/src/js/plugins/youtube.js +++ b/src/js/plugins/youtube.js @@ -64,6 +64,14 @@ function mapQualityUnits(levels) { return utils.dedupe(levels.map(level => mapQualityUnit(level))); } +// Set playback state and trigger change (only on actual change) +function assurePlaybackState(play) { + if (this.media.paused === play) { + this.media.paused = !play; + utils.dispatchEvent.call(this, this.media, play ? 'play' : 'pause'); + } +} + const youtube = { setup() { // Add embed class for responsive @@ -264,10 +272,12 @@ const youtube = { // Create a faux HTML5 API using the YouTube API player.media.play = () => { + assurePlaybackState.call(player, true); instance.playVideo(); }; player.media.pause = () => { + assurePlaybackState.call(player, false); instance.pauseVideo(); }; @@ -285,22 +295,17 @@ const youtube = { return Number(instance.getCurrentTime()); }, set(time) { - // Vimeo will automatically play on seek - const { paused } = player.media; + // If paused, mute audio preventively (YouTube starts playing on seek if the video hasn't been played yet). + if (player.paused) { + player.embed.mute(); + } - // Set seeking flag + // Set seeking state and trigger event player.media.seeking = true; - - // Trigger seeking utils.dispatchEvent.call(player, player.media, 'seeking'); // Seek after events sent instance.seekTo(time); - - // Restore pause state - if (paused) { - player.pause(); - } }, }); @@ -419,6 +424,17 @@ const youtube = { // Reset timer clearInterval(player.timers.playing); + const seeked = player.media.seeking && [ + 1, + 2, + ].includes(event.data); + + if (seeked) { + // Unset seeking and fire seeked event + player.media.seeking = false; + utils.dispatchEvent.call(player, player.media, 'seeked'); + } + // Handle events // -1 Unstarted // 0 Ended @@ -438,7 +454,7 @@ const youtube = { break; case 0: - player.media.paused = true; + assurePlaybackState.call(player, false); // YouTube doesn't support loop for a single video, so mimick it. if (player.media.loop) { @@ -452,42 +468,39 @@ const youtube = { break; case 1: - // If we were seeking, fire seeked event - if (player.media.seeking) { - utils.dispatchEvent.call(player, player.media, 'seeked'); - } - player.media.seeking = false; - - // Only fire play if paused before + // Restore paused state (YouTube starts playing on seek if the video hasn't been played yet) if (player.media.paused) { - utils.dispatchEvent.call(player, player.media, 'play'); + player.media.pause(); + } else { + assurePlaybackState.call(player, true); + + utils.dispatchEvent.call(player, player.media, 'playing'); + + // Poll to get playback progress + player.timers.playing = setInterval(() => { + utils.dispatchEvent.call(player, player.media, 'timeupdate'); + }, 50); + + // Check duration again due to YouTube bug + // https://github.com/sampotts/plyr/issues/374 + // https://code.google.com/p/gdata-issues/issues/detail?id=8690 + if (player.media.duration !== instance.getDuration()) { + player.media.duration = instance.getDuration(); + utils.dispatchEvent.call(player, player.media, 'durationchange'); + } + + // Get quality + controls.setQualityMenu.call(player, mapQualityUnits(instance.getAvailableQualityLevels())); } - player.media.paused = false; - - utils.dispatchEvent.call(player, player.media, 'playing'); - - // Poll to get playback progress - player.timers.playing = setInterval(() => { - utils.dispatchEvent.call(player, player.media, 'timeupdate'); - }, 50); - - // Check duration again due to YouTube bug - // https://github.com/sampotts/plyr/issues/374 - // https://code.google.com/p/gdata-issues/issues/detail?id=8690 - if (player.media.duration !== instance.getDuration()) { - player.media.duration = instance.getDuration(); - utils.dispatchEvent.call(player, player.media, 'durationchange'); - } - - // Get quality - controls.setQualityMenu.call(player, mapQualityUnits(instance.getAvailableQualityLevels())); break; case 2: - player.media.paused = true; - - utils.dispatchEvent.call(player, player.media, 'pause'); + // Restore audio (YouTube starts playing on seek if the video hasn't been played yet) + if (!player.muted) { + player.embed.unMute(); + } + assurePlaybackState.call(player, false); break;