diff --git a/src/lib/viewers/box3d/video360/Video360.scss b/src/lib/viewers/box3d/video360/Video360.scss index 1747a88d2..4558a7141 100644 --- a/src/lib/viewers/box3d/video360/Video360.scss +++ b/src/lib/viewers/box3d/video360/Video360.scss @@ -4,15 +4,16 @@ display: flex; } -.bp-video-360 canvas { - cursor: move; /* fallback if grab cursor is unsupported */ - cursor: grab; -} - -.bp-is-mobile { +.bp-is-mobile .bp-video-360 .bp-media-container { .bp-media-controls-wrapper { /* stylelint-disable declaration-no-important */ opacity: 1 !important; + visibility: visible !important; /* stylelint-enable declaration-no-important */ } } + +.bp-video-360 canvas { + cursor: move; /* fallback if grab cursor is unsupported */ + cursor: grab; +} diff --git a/src/lib/viewers/media/DashViewer.js b/src/lib/viewers/media/DashViewer.js index 77e33b82e..e122a81f1 100644 --- a/src/lib/viewers/media/DashViewer.js +++ b/src/lib/viewers/media/DashViewer.js @@ -368,6 +368,7 @@ const MANIFEST = 'manifest.mpd'; // Make media element visible after resize this.showMedia(); + this.mediaControls.show(); this.mediaContainerEl.focus(); } diff --git a/src/lib/viewers/media/MediaControls.js b/src/lib/viewers/media/MediaControls.js index 896d13cd9..9968a50a4 100644 --- a/src/lib/viewers/media/MediaControls.js +++ b/src/lib/viewers/media/MediaControls.js @@ -9,7 +9,7 @@ import { activationHandler, addActivationListener, removeActivationListener, ins const SHOW_CONTROLS_CLASS = 'bp-media-controls-is-visible'; const PLAYING_CLASS = 'bp-media-is-playing'; const VOLUME_SCRUBBER_EXPAND_CLASS = 'bp-media-controls-volume-scrubber-expand'; -const CONTROLS_AUTO_HIDE_TIMEOUT_IN_MILLIS = 1500; +const CONTROLS_AUTO_HIDE_TIMEOUT_IN_MILLIS = 2000; const VOLUME_LEVEL_CLASS_NAMES = [ 'bp-media-volume-icon-is-mute', 'bp-media-volume-icon-is-low', @@ -613,6 +613,31 @@ const FILMSTRIP_FRAME_HEIGHT = 90; } } + /** + * Toggles the media controls + * + * @return {void} + */ + toggle() { + if (this.isVisible()) { + // Clear all controls hiding blockers to allow hiding + this.preventHiding = false; + this.settings.hide(); + this.hide(); + } else { + this.show(); + } + } + + /** + * Determines if media controls are shown + * + * @return {boolean} If the controls are visible + */ + isVisible() { + return this.wrapperEl.parentNode.classList.contains(SHOW_CONTROLS_CLASS); + } + /** * Resizes the time scrubber * @@ -669,6 +694,11 @@ const FILMSTRIP_FRAME_HEIGHT = 90; // aspect ratio as the original video. this.timeScrubber.getHandleEl().addEventListener('mousedown', this.timeScrubbingStartHandler); + + if (this.hasTouch) { + this.timeScrubberEl.addEventListener('touchstart', this.timeScrubbingStartHandler); + } + this.timeScrubber.getConvertedEl().addEventListener('mousemove', this.filmstripShowHandler); this.timeScrubber.getConvertedEl().addEventListener('mouseleave', this.filmstripHideHandler); @@ -692,16 +722,23 @@ const FILMSTRIP_FRAME_HEIGHT = 90; timeScrubbingStartHandler() { // Flag that we are scrubbing this.isScrubbing = true; + this.preventHiding = true; // Add event listener for the entire document that when mouse up happens // anywhere, consider scrubbing has stopped. This is added on the document // itself so that the user doesn't have to scrub in a straight line. document.addEventListener('mouseup', this.timeScrubbingStopHandler); + if (this.hasTouch) { + document.addEventListener('touchend', this.timeScrubbingStopHandler); + } // Likewise add a mouse move handler to the entire document so that when // the user is scrubbing and they are randomly moving mouse anywhere on the // document, we still continue to show the film strip document.addEventListener('mousemove', this.filmstripShowHandler); + if (this.hasTouch) { + document.addEventListener('touchmove', this.show); + } } /** @@ -714,11 +751,15 @@ const FILMSTRIP_FRAME_HEIGHT = 90; timeScrubbingStopHandler(event) { // Flag that scrubbing is done this.isScrubbing = false; + this.preventHiding = false; // Remove any even listeners that were added when scrubbing started document.removeEventListener('mouseup', this.timeScrubbingStopHandler); document.removeEventListener('mousemove', this.filmstripShowHandler); + document.removeEventListener('touchend', this.timeScrubbingStopHandler); + document.removeEventListener('touchmove', this.show); + if (!this.timeScrubberEl.contains(event.target)) { // Don't hide the filmstrip if we were hovering over the scrubber when // mouse up happened. Since we show film strip on hover. On all other cases diff --git a/src/lib/viewers/media/MediaControls.scss b/src/lib/viewers/media/MediaControls.scss index 73525a000..5938688f6 100644 --- a/src/lib/viewers/media/MediaControls.scss +++ b/src/lib/viewers/media/MediaControls.scss @@ -5,6 +5,7 @@ .bp-media-controls-wrapper:active, .bp-media-controls-wrapper:focus { opacity: 1; + visibility: visible; } .bp-media-controls-wrapper { @@ -17,7 +18,8 @@ overflow: hidden; position: absolute; right: 0; - transition: opacity .3s; + transition: visibility .3s, opacity .3s; + visibility: hidden; width: 100%; } diff --git a/src/lib/viewers/media/VideoBaseViewer.js b/src/lib/viewers/media/VideoBaseViewer.js index f04bff633..01d800110 100644 --- a/src/lib/viewers/media/VideoBaseViewer.js +++ b/src/lib/viewers/media/VideoBaseViewer.js @@ -18,6 +18,8 @@ const CLASS_PLAY_BUTTON = 'bp-media-play-button'; // Video element this.mediaEl = this.mediaContainerEl.appendChild(document.createElement('video')); this.mediaEl.setAttribute('preload', 'auto'); + // Prevents native iOS UI from taking over + this.mediaEl.setAttribute('playsinline', ''); // Play button this.playButtonEl = this.mediaContainerEl.appendChild(document.createElement('div')); @@ -37,7 +39,8 @@ const CLASS_PLAY_BUTTON = 'bp-media-play-button'; destroy() { if (this.mediaEl) { this.mediaEl.removeEventListener('mousemove', this.mousemoveHandler); - this.mediaEl.removeEventListener('click', this.togglePlay); + this.mediaEl.removeEventListener('click', this.pointerHandler); + this.mediaEl.removeEventListener('touchstart', this.pointerHandler); this.mediaEl.removeEventListener('waiting', this.waitingHandler); } @@ -57,6 +60,24 @@ const CLASS_PLAY_BUTTON = 'bp-media-play-button'; loadeddataHandler() { super.loadeddataHandler(); this.showPlayButton(); + this.mediaControls.show(); + } + + /** + * Handler for a pointer event on the media element. + * + * @param {Event} event pointer event, either touch or mouse + * @return {void} + */ + pointerHandler(event) { + if (event.type === 'touchstart') { + // Prevents 'click' event from firing which would pause the video + event.preventDefault(); + event.stopPropagation(); + this.mediaControls.toggle(); + } else if (event.type === 'click') { + this.togglePlay(); + } } /** @@ -132,7 +153,11 @@ const CLASS_PLAY_BUTTON = 'bp-media-play-button'; }, MOUSE_MOVE_TIMEOUT_IN_MILLIS); this.mediaEl.addEventListener('mousemove', this.mousemoveHandler); - this.mediaEl.addEventListener('click', this.togglePlay); + if (this.hasTouch) { + this.mediaEl.addEventListener('touchstart', this.pointerHandler); + } + + this.mediaEl.addEventListener('click', this.pointerHandler); this.mediaEl.addEventListener('waiting', this.waitingHandler); this.playButtonEl.addEventListener('click', this.togglePlay); } diff --git a/src/lib/viewers/media/__tests__/DashViewer-test.js b/src/lib/viewers/media/__tests__/DashViewer-test.js index 92677ea22..9514e7da9 100644 --- a/src/lib/viewers/media/__tests__/DashViewer-test.js +++ b/src/lib/viewers/media/__tests__/DashViewer-test.js @@ -78,7 +78,8 @@ describe('lib/viewers/media/DashViewer', () => { initFilmstrip: () => {}, initSubtitles: () => {}, removeAllListeners: () => {}, - removeListener: () => {} + removeListener: () => {}, + show: sandbox.stub() }; stubs.mockControls = sandbox.mock(dash.mediaControls); @@ -430,6 +431,7 @@ describe('lib/viewers/media/DashViewer', () => { expect(dash.emit).to.be.calledWith('load'); expect(dash.loaded).to.be.true; expect(document.activeElement).to.equal(dash.mediaContainerEl); + expect(dash.mediaControls.show).to.be.called; }); }); diff --git a/src/lib/viewers/media/__tests__/MediaControls-test.js b/src/lib/viewers/media/__tests__/MediaControls-test.js index 70a009ece..1c1b07124 100644 --- a/src/lib/viewers/media/__tests__/MediaControls-test.js +++ b/src/lib/viewers/media/__tests__/MediaControls-test.js @@ -198,7 +198,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('handleRate', () => { + describe('handleRate()', () => { it('should emit the ratechange event', () => { stubs.emit = sandbox.stub(mediaControls, 'emit'); @@ -207,7 +207,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('handleQuality', () => { + describe('handleQuality()', () => { it('should emit the qualitychange event', () => { stubs.emit = sandbox.stub(mediaControls, 'emit'); @@ -216,7 +216,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('handleSubtitle', () => { + describe('handleSubtitle()', () => { it('should emit the subtitlechange event', () => { stubs.emit = sandbox.stub(mediaControls, 'emit'); @@ -225,7 +225,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('setupSettings', () => { + describe('setupSettings()', () => { it('should create a settings obect and bind listeners', () => { const settingsStub = sandbox.stub(Settings.prototype, 'addListener'); @@ -236,7 +236,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('setupScrubbers', () => { + describe('setupScrubbers()', () => { beforeEach(() => { stubs.on = sandbox.stub(Scrubber.prototype, 'on'); }); @@ -268,7 +268,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('formatTime', () => { + describe('formatTime()', () => { it('should correctly format 3 hours', () => { const result = mediaControls.formatTime(10800); expect(result).to.equal('3:00:00'); @@ -300,7 +300,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('setDuration', () => { + describe('setDuration()', () => { beforeEach(() => { mediaControls.durationEl = { textContent: '' @@ -319,7 +319,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('setTimeCode', () => { + describe('setTimeCode()', () => { beforeEach(() => { mediaControls.mediaEl = { textContent: '', @@ -352,7 +352,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('updateProgress', () => { + describe('updateProgress()', () => { it('should correctly set the buffered value of the time scrubber', () => { mediaControls.mediaEl = { buffered: { @@ -369,7 +369,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('toggleMute', () => { + describe('toggleMute()', () => { it('should emit a togglemute message', () => { stubs.emit = sandbox.stub(mediaControls, 'emit'); @@ -378,7 +378,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('togglePlay', () => { + describe('togglePlay()', () => { it('should emit a toggleplayback message', () => { stubs.emit = sandbox.stub(mediaControls, 'emit'); @@ -387,7 +387,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('toggleSubtitles', () => { + describe('toggleSubtitles()', () => { it('should emit a togglesubtitles message', () => { sandbox.stub(mediaControls.settings, 'toggleSubtitles'); stubs.emit = sandbox.stub(mediaControls, 'emit'); @@ -399,7 +399,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('toggleFullscreen', () => { + describe('toggleFullscreen()', () => { beforeEach(() => { stubs.emit = sandbox.stub(mediaControls, 'emit'); stubs.setFullscreenLabel = sandbox.stub(mediaControls, 'setFullscreenLabel'); @@ -416,7 +416,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('setFullscreenLabel', () => { + describe('setFullscreenLabel()', () => { beforeEach(() => { stubs.isFullscreen = sandbox.stub(fullscreen, 'isFullscreen'); stubs.setLabel = sandbox.stub(mediaControls, 'setLabel'); @@ -437,7 +437,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('toggleSettings', () => { + describe('toggleSettings()', () => { beforeEach(() => { stubs.show = sandbox.stub(mediaControls.settings, 'show'); stubs.hide = sandbox.stub(mediaControls.settings, 'hide'); @@ -459,7 +459,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('setLabel', () => { + describe('setLabel()', () => { it('should set the aria label and the title of the given label', () => { const el = document.createElement('button'); @@ -469,7 +469,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('isSettingsVisible', () => { + describe('isSettingsVisible()', () => { it('should return true if the settings exist and are visible', () => { stubs.isVisible = sandbox.stub(mediaControls.settings, 'isVisible').returns(true); @@ -483,7 +483,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('showPauseIcon', () => { + describe('showPauseIcon()', () => { it('should add the playing class to the wrapper el and update the label', () => { stubs.setLabel = sandbox.stub(mediaControls, 'setLabel'); @@ -493,7 +493,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('showPlayIcon', () => { + describe('showPlayIcon()', () => { it('should remove the playing class to the wrapper el and update the label', () => { stubs.setLabel = sandbox.stub(mediaControls, 'setLabel'); @@ -503,7 +503,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('updateVolumeIcon', () => { + describe('updateVolumeIcon()', () => { beforeEach(() => { mediaControls.setupScrubbers(); stubs.setValue = sandbox.stub(mediaControls.volScrubber, 'setValue'); @@ -538,7 +538,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('attachEventHandlers', () => { + describe('attachEventHandlers()', () => { beforeEach(() => { stubs.wrapperAddEventListener = sandbox.stub(mediaControls.wrapperEl, 'addEventListener'); stubs.addActivationListener = sandbox.stub(util, 'addActivationListener'); @@ -573,7 +573,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('mouseenterHandler', () => { + describe('mouseenterHandler()', () => { it('should set preventHiding to true and show the controls', () => { stubs.show = sandbox.stub(mediaControls, 'show'); @@ -583,7 +583,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('mouseleaveHandler', () => { + describe('mouseleaveHandler()', () => { it('should allow hiding via setHiding and show the controls', () => { stubs.show = sandbox.stub(mediaControls, 'show'); @@ -593,10 +593,10 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('show', () => { + describe('show()', () => { beforeEach(() => { stubs.showControlClass = 'bp-media-controls-is-visible'; - stubs.timeout = 1501; + stubs.timeout = 2001; stubs.hide = sandbox.stub(mediaControls, 'hide'); clock = sinon.useFakeTimers(); }); @@ -622,7 +622,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('hide', () => { + describe('hide()', () => { beforeEach(() => { stubs.isSettingsVisible = sandbox.stub(mediaControls, 'isSettingsVisible'); stubs.show = sandbox.stub(mediaControls, 'show'); @@ -653,7 +653,48 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('resizeTimeScrubber', () => { + describe('toggle()', () => { + beforeEach(() => { + stubs.isVisible = sandbox.stub(mediaControls, 'isVisible'); + stubs.hide = sandbox.stub(mediaControls, 'hide'); + stubs.show = sandbox.stub(mediaControls, 'show'); + }); + + it('should hide the settings and remove preventHiding if the controls are visible', () => { + stubs.isVisible.returns(true); + + mediaControls.toggle(); + expect(stubs.hide).to.be.called; + expect(mediaControls.preventHiding).to.be.false; + }); + + it('should show the controls if they are not visible', () => { + stubs.isVisible.returns(false); + + mediaControls.toggle(); + expect(stubs.show).to.be.called; + }); + }); + + describe('isVisible()', () => { + beforeEach(() => { + stubs.wrapperParent = document.createElement('div') + mediaControls.wrapperEl = stubs.wrapperParent.appendChild(document.createElement('div')); + }); + + it('should return false if the controls show class is missing', () => { + const result = mediaControls.isVisible(); + expect(result).to.be.false; + }); + + it('should return true if the controls show class is present', () => { + stubs.wrapperParent.classList.add('bp-media-controls-is-visible'); + const result = mediaControls.isVisible(); + expect(result).to.be.true; + }); + }); + + describe('resizeTimeScrubber()', () => { it('should resize the time scrubber', () => { mediaControls.setupScrubbers(); stubs.resize = sandbox.stub(mediaControls.timeScrubber, 'resize'); @@ -663,7 +704,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('setFilmstrip', () => { + describe('setFilmstrip()', () => { it('should set the filmstrip source to the provided URL', () => { mediaControls.filmstripEl = { src: '' @@ -676,7 +717,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('initFilmstrip', () => { + describe('initFilmstrip()', () => { beforeEach(() => { stubs.status = { getPromise: sandbox.stub().returns(Promise.resolve()) @@ -687,6 +728,10 @@ describe('lib/viewers/media/MediaControls', () => { mediaControls.timeScrubber.getConvertedEl(), 'addEventListener' ); + + mediaControls.timeScrubberEl = { + addEventListener: sandbox.stub() + } stubs.setFilmstrip = sandbox.stub(mediaControls, 'setFilmstrip'); }); @@ -722,24 +767,40 @@ describe('lib/viewers/media/MediaControls', () => { ); }); + it('should add the touchstart eventListener if touch is detected', () => { + mediaControls.hasTouch = true; + mediaControls.initFilmstrip('url', stubs.status, '380', 1); + expect(mediaControls.timeScrubberEl.addEventListener).to.be.calledWith('touchstart', mediaControls.timeScrubbingStartHandler) + }); + it('should add the onload function to the filmstrip', () => { mediaControls.initFilmstrip('url', stubs.status, '380', 1); expect(typeof mediaControls.filmstripEl.onload === 'function'); }); }); - describe('timeScrubbingStartHandler', () => { - it('should set isScrubbing to true and add show and stop handlers', () => { + describe('timeScrubbingStartHandler()', () => { + it('should set isScrubbing to true, preventHiding to true, and add show and stop handlers', () => { stubs.addEventListener = sandbox.stub(document, 'addEventListener'); mediaControls.timeScrubbingStartHandler(); expect(mediaControls.isScrubbing).to.equal(true); + expect(mediaControls.preventHiding).to.equal(true); expect(stubs.addEventListener).to.be.calledWith('mouseup', mediaControls.timeScrubbingStopHandler); expect(stubs.addEventListener).to.be.calledWith('mousemove', mediaControls.filmstripShowHandler); }); + + it('should add show and stop touch events if touch is present', () => { + mediaControls.hasTouch = true; + stubs.addEventListener = sandbox.stub(document, 'addEventListener'); + + mediaControls.timeScrubbingStartHandler(); + expect(stubs.addEventListener).to.be.calledWith('touchend', mediaControls.timeScrubbingStopHandler); + expect(stubs.addEventListener).to.be.calledWith('touchmove', mediaControls.show); + }); }); - describe('timeScrubbingStopHandler', () => { + describe('timeScrubbingStopHandler()', () => { beforeEach(() => { mediaControls.setupScrubbers(); stubs.event = { @@ -749,13 +810,22 @@ describe('lib/viewers/media/MediaControls', () => { mediaControls.filmstripContainerEl = document.createElement('div'); }); - it('should set isScrubbing to false and remove show and stop handlers', () => { + it('should set isScrubbing to false, preventHiding to false, and remove show and stop handlers', () => { mediaControls.timeScrubbingStopHandler(stubs.event); expect(mediaControls.isScrubbing).to.equal(false); + expect(mediaControls.preventHiding).to.equal(false); expect(stubs.removeEventListener).to.be.calledWith('mouseup', mediaControls.timeScrubbingStopHandler); expect(stubs.removeEventListener).to.be.calledWith('mousemove', mediaControls.filmstripShowHandler); }); + it('should remove touch show and stop handlers if touch is present', () => { + mediaControls.hasTouch = true; + mediaControls.timeScrubbingStopHandler(stubs.event); + + expect(stubs.removeEventListener).to.be.calledWith('touchend', mediaControls.timeScrubbingStopHandler); + expect(stubs.removeEventListener).to.be.calledWith('touchmove', mediaControls.show); + }); + it('should hide the filmstrip if it is not being hovered over', () => { stubs.event.target = document.createElement('div'); @@ -764,7 +834,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('filmstripShowHandler', () => { + describe('filmstripShowHandler()', () => { beforeEach(() => { stubs.getBoundingClientRect = sandbox.stub(mediaControls.containerEl, 'getBoundingClientRect').returns({ left: 0, @@ -810,7 +880,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('computeFilmstripPositions', () => { + describe('computeFilmstripPositions()', () => { it('should compute correct positions when filmstrip not ready', () => { mediaControls.mediaEl = { duration: 100 @@ -902,7 +972,7 @@ describe('lib/viewers/media/MediaControls', () => { }); }); - describe('filmstripHideHandler', () => { + describe('filmstripHideHandler()', () => { beforeEach(() => { stubs.status = { getPromise: sandbox.stub().returns(Promise.resolve()) diff --git a/src/lib/viewers/media/__tests__/VideoBaseViewer-test.js b/src/lib/viewers/media/__tests__/VideoBaseViewer-test.js index e7677f4fd..ecb20b284 100644 --- a/src/lib/viewers/media/__tests__/VideoBaseViewer-test.js +++ b/src/lib/viewers/media/__tests__/VideoBaseViewer-test.js @@ -32,6 +32,16 @@ describe('lib/viewers/media/VideoBaseViewer', () => { url_template: 'www.netflix.com' } }); + videoBase.mediaControls = { + on: sandbox.stub(), + addListener: sandbox.stub(), + removeAllListeners: sandbox.stub(), + destroy: sandbox.stub(), + show: sandbox.stub(), + toggle: sandbox.stub(), + resizeTimeScrubber: sandbox.stub(), + }; + Object.defineProperty(BaseViewer.prototype, 'setup', { value: sandbox.stub() }); videoBase.containerEl = containerEl; videoBase.setup(); @@ -67,6 +77,7 @@ describe('lib/viewers/media/VideoBaseViewer', () => { videoBase.setup(); expect(videoBase.mediaEl.getAttribute('preload')).to.equal('auto'); + expect(videoBase.mediaEl.getAttribute('playsinline')).to.equal(''); expect(videoBase.playButtonEl.className).to.equal('bp-media-play-button bp-is-hidden'); expect(videoBase.playButtonEl.innerHTML).contains(' { videoBase.destroy(); expect(videoBase.mediaEl.removeEventListener).to.be.calledWith('mousemove', videoBase.mousemoveHandler); - expect(videoBase.mediaEl.removeEventListener).to.be.calledWith('click', videoBase.togglePlay); + expect(videoBase.mediaEl.removeEventListener).to.be.calledWith('click', videoBase.pointerHandler); + expect(videoBase.mediaEl.removeEventListener).to.be.calledWith('touchstart', videoBase.pointerHandler); expect(videoBase.mediaEl.removeEventListener).to.be.calledWith('waiting', videoBase.waitingHandler); expect(videoBase.playButtonEl.removeEventListener).to.be.calledWith('click', videoBase.togglePlay); }); @@ -106,6 +118,32 @@ describe('lib/viewers/media/VideoBaseViewer', () => { }); }); + describe('pointerHandler()', () => { + it('should prevent default, stop propagation, and toggle the controls on touchstart', () => { + const event = { + type: 'touchstart', + preventDefault: sandbox.stub(), + stopPropagation: sandbox.stub() + } + + videoBase.pointerHandler(event); + expect(event.stopPropagation).to.be.called; + expect(event.preventDefault).to.be.called; + expect(videoBase.mediaControls.toggle).to.be.called; + + }); + + it('should toggle play on click', () => { + const event = { + type: 'click', + } + const togglePlayStub = sandbox.stub(videoBase, 'togglePlay'); + + videoBase.pointerHandler(event); + expect(togglePlayStub).to.be.called; + }); + }); + describe('playingHandler()', () => { it('should hide the play button', () => { sandbox.stub(videoBase, 'hidePlayButton'); @@ -137,12 +175,6 @@ describe('lib/viewers/media/VideoBaseViewer', () => { describe('addEventListenersForMediaControls()', () => { it('should add a handler for toggling fullscreen', () => { - videoBase.mediaControls = { - on: sandbox.stub(), - addListener: sandbox.stub(), - removeAllListeners: sandbox.stub(), - destroy: sandbox.stub() - }; videoBase.addEventListenersForMediaControls(); expect(videoBase.mediaControls.on).to.be.calledWith('togglefullscreen', sinon.match.func); }); @@ -157,7 +189,8 @@ describe('lib/viewers/media/VideoBaseViewer', () => { expect(videoBase.mousemoveHandler).to.be.a.func; expect(videoBase.mediaEl.addEventListener).to.be.calledWith('mousemove', videoBase.mousemoveHandler); - expect(videoBase.mediaEl.addEventListener).to.be.calledWith('click', videoBase.togglePlay); + expect(videoBase.mediaEl.addEventListener).to.be.calledWith('click', videoBase.pointerHandler); + expect(videoBase.mediaEl.addEventListener).to.be.calledWith('touchstart', videoBase.pointerHandler); expect(videoBase.mediaEl.addEventListener).to.be.calledWith('waiting', videoBase.waitingHandler); expect(videoBase.playButtonEl.addEventListener).to.be.calledWith('click', videoBase.togglePlay); }); @@ -165,11 +198,6 @@ describe('lib/viewers/media/VideoBaseViewer', () => { describe('resize()', () => { it('should resize the time scrubber', () => { - videoBase.mediaControls = { - resizeTimeScrubber: sandbox.stub(), - removeAllListeners: sandbox.stub(), - destroy: sandbox.stub() - }; videoBase.resize(); expect(videoBase.mediaControls.resizeTimeScrubber).to.be.called; });