From a9476675042d4ce2cb42bf9e3f3139198e7a323c Mon Sep 17 00:00:00 2001 From: Jonathan Raoult Date: Thu, 22 Oct 2015 21:57:20 +1100 Subject: [PATCH] Fixed bug in Safari 9 where video wouldn't play sometimes: - deferred resolution of youtube player load returned promise --- src/main/playerYoutube.js | 8 ++- .../unit/{defer.js => enqueueMicrotask.js} | 10 ++-- src/test/unit/playbackSlotSpec.js | 10 ++-- src/test/unit/sequencerSpec.js | 54 +++++++++---------- 4 files changed, 42 insertions(+), 40 deletions(-) rename src/test/unit/{defer.js => enqueueMicrotask.js} (76%) diff --git a/src/main/playerYoutube.js b/src/main/playerYoutube.js index fc15acc..53af10c 100644 --- a/src/main/playerYoutube.js +++ b/src/main/playerYoutube.js @@ -6,6 +6,7 @@ var animationGroup = require('./animationGroup'), animationFade = require('./animationFade'), isNumber = require('lodash/lang/isNumber'), has = require('lodash/object/has'), + defer = require('lodash/function/defer'), EventEmitter = require('events').EventEmitter; var _ytApiPromise = new Promise(function ytApiPromiseExecutor(resolve, reject) { @@ -175,7 +176,12 @@ function playerYoutube(config) { if (evt.data === YT.PlayerState.PLAYING) { unbindLoadListeners(); ytPlayer.pauseVideo(); - resolve(); + + // sometimes videos are not playing in Safari 9 until a re-layout is triggered + // seems like resolving the load promise in the next macro task fixes it + defer(function() { + resolve(); + }); } } diff --git a/src/test/unit/defer.js b/src/test/unit/enqueueMicrotask.js similarity index 76% rename from src/test/unit/defer.js rename to src/test/unit/enqueueMicrotask.js index c2be9c2..3fbdded 100644 --- a/src/test/unit/defer.js +++ b/src/test/unit/enqueueMicrotask.js @@ -9,7 +9,7 @@ var isFunction = require('lodash/lang/isFunction'), slice = require('lodash/array/slice'); /** - * Defers executing the `func` function until the current call stack has cleared. + * Defers executing the `func` function until the current microtasks stack has cleared. * Additional arguments will be provided to `func` when it is invoked. * * It is a version inspired from the lodash one but based on Promise instead of timeout which @@ -17,12 +17,8 @@ var isFunction = require('lodash/lang/isFunction'), * * @param {Function} func The function to defer. * @param {...*} [arg] Arguments to invoke the function with. - * @example - * - * _.defer(function(text) { console.log(text); }, 'deferred'); - * // logs 'deferred' after one or more milliseconds */ -function defer(func) { +function enqueueMicrotask(func) { if (!isFunction(func)) { throw new TypeError(); } @@ -30,4 +26,4 @@ function defer(func) { Promise.resolve().then(function() { func.apply(undefined, args); }); } -module.exports = defer; +module.exports = enqueueMicrotask; diff --git a/src/test/unit/playbackSlotSpec.js b/src/test/unit/playbackSlotSpec.js index 0765722..0b18393 100644 --- a/src/test/unit/playbackSlotSpec.js +++ b/src/test/unit/playbackSlotSpec.js @@ -4,7 +4,7 @@ var playbackSlot = require('../../main/playbackSlot'), playersPoolMock = require('./playersPoolMock'), - defer = require('./defer'), + enqueueMicrotask = require('./enqueueMicrotask'), defaults = require('lodash/object/defaults'), constant = require('lodash/utility/constant'), identity = require('lodash/utility/identity'), @@ -147,7 +147,7 @@ describe('A player slot', function() { var config = {audioGain: 0}; slot.start(config); - defer(function() { + enqueueMicrotask(function() { expect(playSpy).not.toHaveBeenCalled(); done(); @@ -176,7 +176,7 @@ describe('A player slot', function() { slot.end(); - defer(function() { + enqueueMicrotask(function() { expect(fadeOutSpy).not.toHaveBeenCalled(); done(); @@ -298,7 +298,7 @@ describe('A player slot', function() { slot.load().then(function() { slot.start(); - defer(function() { + enqueueMicrotask(function() { slot.end().then(function() { expect(fadeOutSpy).toHaveBeenCalled(); @@ -355,7 +355,7 @@ describe('A player slot', function() { slot.start(); - defer(function() { + enqueueMicrotask(function() { // we are going to execute 3 "cues handler" cycles each time with a different currentTime value playerProps.currentTime = playerProps.duration * 1 / 4; diff --git a/src/test/unit/sequencerSpec.js b/src/test/unit/sequencerSpec.js index 123fb05..585e4ab 100644 --- a/src/test/unit/sequencerSpec.js +++ b/src/test/unit/sequencerSpec.js @@ -4,7 +4,7 @@ var sequencer = require('../../main/sequencer'), playbackSlotMock = require('./playbackSlotMock'), - defer = require('./defer'), + enqueueMicrotask = require('./enqueueMicrotask'), describe = jasmine.getEnv().describe, beforeEach = jasmine.getEnv().beforeEach, it = jasmine.getEnv().it, @@ -86,7 +86,7 @@ describe('A sequencer', function() { seq.play(); - defer(function() { + enqueueMicrotask(function() { expect(nextEntryProducerSpy).not.toHaveBeenCalledWith(null); done(); @@ -137,13 +137,13 @@ describe('A sequencer', function() { _entries[4] ]); - defer(done); + enqueueMicrotask(done); }); seq.skip(_entries[0]); seq.play(); - defer(function() { + enqueueMicrotask(function() { seq.skip(_entries[3]); }); }); @@ -169,9 +169,9 @@ describe('A sequencer', function() { // fake auto ending slot.start.and.callFake(function() { - defer(function() { + enqueueMicrotask(function() { producerCfg.endingSoon(); - defer(function() { + enqueueMicrotask(function() { producerCfg.ending(); }); }); @@ -211,7 +211,7 @@ describe('A sequencer', function() { new Promise(function(resolve) { (function deferredWhile(idx) { if (idx < _entries.length) { - defer(function() { + enqueueMicrotask(function() { seq.skip(_entries[idx]); deferredWhile(idx + 1); }); @@ -250,8 +250,8 @@ describe('A sequencer', function() { var slot = seqDefaultCfg.playbackSlotProducer(producerCfg); slot.end.and.callFake(function() { // doubled defer to simulate how actual implementation behaves - defer(function() { - defer(function() { + enqueueMicrotask(function() { + enqueueMicrotask(function() { producerCfg.ending(); }); }); @@ -266,7 +266,7 @@ describe('A sequencer', function() { seq.skip(_entries[0]); seq.play(); - defer(function() { + enqueueMicrotask(function() { seq.skip(_entries[1]); }); @@ -286,7 +286,7 @@ describe('A sequencer', function() { slot = seqDefaultCfg.playbackSlotProducer(producerCfg); slot.load.and.callFake(function() { - defer(runChecks); + enqueueMicrotask(runChecks); return Promise.resolve(); }); @@ -323,7 +323,7 @@ describe('A sequencer', function() { var step = steps[slotsIdx]; slot.load.and.callFake(function() { - defer(step); + enqueueMicrotask(step); return Promise.resolve(); }); @@ -345,7 +345,7 @@ describe('A sequencer', function() { function step2() { seq.play(); - defer(function() { + enqueueMicrotask(function() { expect(slots[0].proceed).not.toHaveBeenCalled(); expect(slots[1].proceed).toHaveBeenCalled(); @@ -381,10 +381,10 @@ describe('A sequencer', function() { seq.play(); seq.skip(_entries[0]); - defer(function() { + enqueueMicrotask(function() { seq.skip(_entries[1]); - defer(function() { + enqueueMicrotask(function() { seq.stop(); }); }); @@ -411,7 +411,7 @@ describe('A sequencer', function() { slots.push(slot); if (slot.entry === _entries[2]) { - defer(function() { + enqueueMicrotask(function() { runChecks(); done(); }); @@ -425,7 +425,7 @@ describe('A sequencer', function() { seq.play(); seq.skip(entries[0]); - defer(function() { + enqueueMicrotask(function() { // remove the next pullAt(entries, 1); seq.checkNextEntry(); @@ -455,7 +455,7 @@ describe('A sequencer', function() { slots.push(slot); if (slot.entry === _entries[1]) { - defer(function() { + enqueueMicrotask(function() { runChecks(); done(); }); @@ -469,7 +469,7 @@ describe('A sequencer', function() { seq.play(); seq.skip(entries[0]); - defer(function() { + enqueueMicrotask(function() { // add the next entries.push(_entries[1]); seq.checkNextEntry(); @@ -498,7 +498,7 @@ describe('A sequencer', function() { slots.push(slot); if (slot.entry === _entries[1]) { - defer(function() { + enqueueMicrotask(function() { runChecks(); done(); }); @@ -512,7 +512,7 @@ describe('A sequencer', function() { seq.play(); seq.skip(entries[0]); - defer(function() { + enqueueMicrotask(function() { seq.checkNextEntry(); }); @@ -574,7 +574,7 @@ describe('A sequencer', function() { } else { slot.start.and.callFake(function() { startedSlot = slot; - defer(runChecks); + enqueueMicrotask(runChecks); }); } return slot; @@ -601,7 +601,7 @@ describe('A sequencer', function() { var slot = seqDefaultCfg.playbackSlotProducer(producerCfg); if (slot.entry === _entries[0]) { slot.start.and.callFake(function() { - defer(producerCfg.ending); + enqueueMicrotask(producerCfg.ending); }); } else if (contains(_entries.slice(1, lastFailingEntryIdx + 1), slot.entry)) { // make the all slots for the entries from 1 to 3 failing on load @@ -609,7 +609,7 @@ describe('A sequencer', function() { } else { slot.start.and.callFake(function() { startedSlot = slot; - defer(runChecks); + enqueueMicrotask(runChecks); }); } return slot; @@ -664,7 +664,7 @@ describe('A sequencer', function() { var slot = seqDefaultCfg.playbackSlotProducer(producerCfg); if (slot.entry === _entries[0]) { slot.start.and.callFake(function() { - defer(producerCfg.ending); + enqueueMicrotask(producerCfg.ending); }); } else { // make all the slots for the other entries failing on load @@ -697,7 +697,7 @@ describe('A sequencer', function() { seq.play(); seq.skip(_entries[0]); - defer(function() { + enqueueMicrotask(function() { seq.skip(_entries[1]); }); @@ -736,7 +736,7 @@ describe('A sequencer', function() { seq.play(); seq.skip(_entries[0]); - defer(function() { + enqueueMicrotask(function() { // entry 1 will fail to load and the sequencer will try entry 2 seq.skip(_entries[1]); });