diff --git a/.travis.yml b/.travis.yml index a5819d0fc..833f999ac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,4 @@ +# @chrome @firefox @edge @safari @ie @android @ios language: node_js node_js: - '8' @@ -14,9 +15,15 @@ aliases: sauce_connect: true script: yarn run functional-tests-ci cache: yarn - - &sauce-labs-mobile + - &sauce-labs-ios <<: *sauce-labs script: yarn run functional-tests-ci --grep @mobile + - &sauce-labs-chrome + <<: *sauce-labs + script: yarn run functional-tests-ci --grep @chrome + - &sauce-labs-firefox + <<: *sauce-labs + script: yarn run functional-tests-ci --grep @firefox stages: - ci - name: sauce-labs - windows 10 @@ -33,33 +40,37 @@ jobs: - <<: *sauce-labs stage: sauce-labs - windows 10 env: BROWSER_PLATFORM="Windows 10" BROWSER_NAME="MicrosoftEdge" - - <<: *sauce-labs + script: yarn run functional-tests-ci --grep @edge + - <<: *sauce-labs-chrome stage: sauce-labs - windows 10 env: BROWSER_PLATFORM="Windows 10" BROWSER_NAME="chrome" - - <<: *sauce-labs + - <<: *sauce-labs-firefox stage: sauce-labs - windows 10 env: BROWSER_PLATFORM="Windows 10" BROWSER_NAME="firefox" - <<: *sauce-labs stage: sauce-labs - windows 10 env: BROWSER_PLATFORM="Windows 10" BROWSER_NAME="internet explorer" + script: yarn run functional-tests-ci --grep @ie - <<: *sauce-labs stage: sauce-labs - osx env: BROWSER_PLATFORM="macOS 10.13" BROWSER_NAME="safari" - - <<: *sauce-labs + script: yarn run functional-tests-ci --grep @safari + - <<: *sauce-labs-chrome stage: sauce-labs - osx env: BROWSER_PLATFORM="macOS 10.13" BROWSER_NAME="chrome" - - <<: *sauce-labs + - <<: *sauce-labs-firefox stage: sauce-labs - osx env: BROWSER_PLATFORM="macOS 10.13" BROWSER_NAME="firefox" - - <<: *sauce-labs-mobile + - <<: *sauce-labs-ios stage: sauce-labs - mobile env: BROWSER_PLATFORM="iOS" DEVICE_NAME="iPhone X Simulator" PLATFORM_VERSION="11.2" BROWSER_NAME="Safari" - - <<: *sauce-labs-mobile + - <<: *sauce-labs-ios stage: sauce-labs - mobile env: BROWSER_PLATFORM="iOS" DEVICE_NAME="iPhone 6 Simulator" PLATFORM_VERSION="11.2" BROWSER_NAME="Safari" - - <<: *sauce-labs-mobile + - <<: *sauce-labs-ios stage: sauce-labs - mobile env: BROWSER_PLATFORM="iOS" DEVICE_NAME="iPad Simulator" PLATFORM_VERSION="11.2" BROWSER_NAME="Safari" - - <<: *sauce-labs-mobile + - <<: *sauce-labs stage: sauce-labs - mobile - env: BROWSER_PLATFORM="Android" DEVICE_NAME="Android Emulator" PLATFORM_VERSION="6.0" BROWSER_NAME="Chrome" + env: BROWSER_PLATFORM="Android" DEVICE_NAME="Android GoogleAPI Emulator" PLATFORM_VERSION="7.1" BROWSER_NAME="Chrome" + script: yarn run functional-tests-ci --grep @android diff --git a/README.md b/README.md index 90ae1faef..0d9744dea 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,7 @@ preview.show(fileId, accessToken, { | File Option | Description | | --- | --- | | fileVersionId | File version ID to preview. This must be a valid non-current file version ID. Use [Get Versions](https://developer.box.com/reference#view-versions-of-a-file) to fetch a list of file versions | +| startAt | Object with unit and value properties indicating where to start the preview at. Current supported units are 'seconds' for media and 'pages' for documents. | Access Token ------------ diff --git a/codecept.conf.js b/codecept.conf.js index ab74bdce4..4a0477508 100644 --- a/codecept.conf.js +++ b/codecept.conf.js @@ -1,4 +1,3 @@ -const DEFAULT_WAIT_TIME = 90000; // 90 seconds const { SAUCE_USERNAME, SAUCE_ACCESS_KEY, @@ -8,7 +7,8 @@ const { BROWSER_VERSION = 'latest', BROWSER_PLATFORM, PLATFORM_VERSION, - DEVICE_NAME + DEVICE_NAME, + DEFAULT_WAIT_TIME = 90000 } = process.env; const MOBILE_PLATFORMS = ['iOS', 'Android']; @@ -53,7 +53,9 @@ if (typeof SAUCE_USERNAME === 'undefined') { platformVersion: PLATFORM_VERSION, deviceName: DEVICE_NAME, deviceOrientation: 'portrait', - appiumVersion: '1.7.2' + appiumVersion: '1.7.2', + platformName: BROWSER_PLATFORM + }); helperObj.Appium = mixedInSauceObj; } diff --git a/functional-tests/constants.js b/functional-tests/constants.js index 961f5ac98..c93c79485 100644 --- a/functional-tests/constants.js +++ b/functional-tests/constants.js @@ -1,4 +1,15 @@ // Unfortunately node doesnt support native imports (yet) - exports.SELECTOR_BOX_PREVIEW_BTN_DOWNLOAD = '.bp-btn-download'; exports.SELECTOR_DOWNLOAD_IFRAME = '#downloadiframe'; +exports.SELECTOR_NAVIGATION_LEFT = '.bp-navigate-left'; +exports.SELECTOR_NAVIGATION_RIGHT = '.bp-navigate-right'; +exports.SELECTOR_BOX_PREVIEW_LOADED = '.bp-loaded'; +exports.SELECTOR_BOX_PREVIEW_LOGO = '.bp-default-logo'; +exports.CLASS_BOX_PREVIEW_LOADING_WRAPPER = '.bp-loading-wrapper'; +exports.SELECTOR_DOC_CURRENT_PAGE = '.bp-current-page'; +exports.SELECTOR_MEDIA_TIMESTAMP = '.bp-media-controls-timecode'; +exports.SELECTOR_BOX_PREVIEW = '.bp'; +exports.SELECTOR_BOX_PREVIEW_DOC = '.bp-doc'; +exports.SELECTOR_BOX_PREVIEW_MP3 = '.bp-media-mp3'; +exports.SELECTOR_BOX_PREVIEW_DASH = '.bp-media-dash'; +exports.SELECTOR_BOX_PREVIEW_MP4 = '.bp-media-mp4'; diff --git a/functional-tests/file-options.html b/functional-tests/file-options.html new file mode 100644 index 000000000..190db08d0 --- /dev/null +++ b/functional-tests/file-options.html @@ -0,0 +1,72 @@ + + + +
+ + + + + + + + + + + + + + diff --git a/functional-tests/fileOptions_test.js b/functional-tests/fileOptions_test.js new file mode 100644 index 000000000..398a6a2af --- /dev/null +++ b/functional-tests/fileOptions_test.js @@ -0,0 +1,102 @@ +const { + SELECTOR_BOX_PREVIEW_LOADED, + SELECTOR_MEDIA_TIMESTAMP, + SELECTOR_DOC_CURRENT_PAGE, + SELECTOR_BOX_PREVIEW_LOGO, + CLASS_BOX_PREVIEW_LOADING_WRAPPER, + SELECTOR_BOX_PREVIEW_DOC, + SELECTOR_BOX_PREVIEW_MP3, + SELECTOR_BOX_PREVIEW_DASH, + SELECTOR_BOX_PREVIEW_MP4 +} = require('./constants'); + +const { navigateToNextItem, makeNavAppear, navigateToPrevItem } = require('./helpers'); + +const { CI } = process.env; +const DOC_START = '2'; +const DASH_START = '0:15'; +const MP3_START = '0:03'; +const MP4_START = '0:10'; +const SELECTOR_VIDEO = 'video'; + +Feature('File Options', { retries: CI ? 3 : 0 }); + +Before((I) => { + I.amOnPage('/functional-tests/file-options.html'); + I.waitForElement(SELECTOR_BOX_PREVIEW_LOADED); + I.waitForElement(SELECTOR_BOX_PREVIEW_LOGO); + I.waitForElement(SELECTOR_BOX_PREVIEW_DOC); +}); + +// Excludes ie +Scenario( + 'Check preview starts at correct spot for all file types @ci @chrome @firefox @edge @safari @android @ios', + (I) => { + // document + makeNavAppear(I); + I.waitForVisible(SELECTOR_DOC_CURRENT_PAGE); + I.seeTextEquals(DOC_START, SELECTOR_DOC_CURRENT_PAGE); + navigateToNextItem(I); + + // video (dash) + I.waitForElement(SELECTOR_BOX_PREVIEW_LOADED); + I.waitForElement(SELECTOR_BOX_PREVIEW_DASH); + makeNavAppear(I, SELECTOR_VIDEO); + I.waitForVisible(SELECTOR_MEDIA_TIMESTAMP); + I.seeTextEquals(DASH_START, SELECTOR_MEDIA_TIMESTAMP); + navigateToNextItem(I); + + // mp3 + I.waitForElement(SELECTOR_BOX_PREVIEW_MP3); + makeNavAppear(I); + I.waitForVisible(SELECTOR_MEDIA_TIMESTAMP); + I.seeTextEquals(MP3_START, SELECTOR_MEDIA_TIMESTAMP); + + // video (mp4) + /* eslint-disable prefer-arrow-callback */ + I.executeScript(function() { + window.disableDash(); + }); + I.waitForElement(CLASS_BOX_PREVIEW_LOADING_WRAPPER); + /* eslint-enable prefer-arrow-callback */ + I.waitForElement(SELECTOR_BOX_PREVIEW_LOADED); + I.waitForElement(SELECTOR_BOX_PREVIEW_MP4); + + makeNavAppear(I, SELECTOR_VIDEO); + I.waitForVisible(SELECTOR_MEDIA_TIMESTAMP); + I.seeTextEquals(MP4_START, SELECTOR_MEDIA_TIMESTAMP); + } +); + +// Sacuelabs ie11 doesn't like audio files +Scenario('Check preview starts at correct spot for all file types @ci @ie', (I) => { + // document + makeNavAppear(I); + I.waitForVisible(SELECTOR_DOC_CURRENT_PAGE); + I.seeTextEquals(DOC_START, SELECTOR_DOC_CURRENT_PAGE); + navigateToNextItem(I); + + // video (dash) + I.waitForElement(SELECTOR_BOX_PREVIEW_DASH); + makeNavAppear(I, SELECTOR_VIDEO); + I.waitForVisible(SELECTOR_MEDIA_TIMESTAMP); + I.seeTextEquals(DASH_START, SELECTOR_MEDIA_TIMESTAMP); + + // mp3 is not supported :( + navigateToPrevItem(I); + // video (mp4) + /* eslint-disable prefer-arrow-callback */ + I.waitForElement(SELECTOR_BOX_PREVIEW_LOADED); + + I.executeScript(function() { + window.disableDash(); + }); + /* eslint-enable prefer-arrow-callback */ + + I.waitForElement(SELECTOR_BOX_PREVIEW_LOADED); + I.waitForElement(SELECTOR_BOX_PREVIEW_MP4); + + makeNavAppear(I, SELECTOR_VIDEO); + I.waitForVisible(SELECTOR_MEDIA_TIMESTAMP); + I.seeTextEquals(MP4_START, SELECTOR_MEDIA_TIMESTAMP); +}); diff --git a/functional-tests/header_test.js b/functional-tests/header_test.js index 7b466b7d2..1e7c72eb7 100644 --- a/functional-tests/header_test.js +++ b/functional-tests/header_test.js @@ -1,13 +1,15 @@ const { SELECTOR_BOX_PREVIEW_BTN_DOWNLOAD, SELECTOR_DOWNLOAD_IFRAME } = require('./constants'); const { expect } = require('chai'); -Feature('Header', { retries: 3 }); +const { CI } = process.env; + +Feature('Header', { retries: CI ? 3 : 0 }); Before((I) => { I.amOnPage('/functional-tests/index.html'); }); -Scenario('Download the file @ci', function*(I) { +Scenario('Download the file @ci @chrome @firefox @edge @safari @ie', function*(I) { I.waitForVisible(SELECTOR_BOX_PREVIEW_BTN_DOWNLOAD); I.click(SELECTOR_BOX_PREVIEW_BTN_DOWNLOAD); I.waitForElement(SELECTOR_DOWNLOAD_IFRAME); diff --git a/functional-tests/helpers.js b/functional-tests/helpers.js new file mode 100644 index 000000000..cac381b14 --- /dev/null +++ b/functional-tests/helpers.js @@ -0,0 +1,70 @@ +const { SELECTOR_BOX_PREVIEW, CLASS_BOX_PREVIEW_LOADING_WRAPPER } = require('./constants'); + +/** + * Makes the navigation arrows appear in preview + * This currently doesnt work in firefox + * + * @param {Object} I the codeceptjs I + * + * @return {void} + */ +function makeNavAppear(I, selector = SELECTOR_BOX_PREVIEW) { + I.waitForElement(selector); + + /* eslint-disable prefer-arrow-callback, no-var */ + I.executeScript(function(sel) { + var count = 0; + var el = document.querySelector(sel); + + /** + * Simulates click and mousemove events for mobile & desktop browsers for + * controls to appear + * + * @return {void} + */ + function simulateEvents() { + /** + * Cross browswer event creation + * @param {string} eventName the event name + * @return {Event} the event + */ + function createNewEvent(eventName) { + var event; + if (typeof Event === 'function') { + event = new Event(eventName, { bubbles: true }); + } else { + event = document.createEvent('Event'); + event.initEvent(eventName, true, true); + } + + return event; + } + + el.dispatchEvent(createNewEvent('mousemove')); + el.dispatchEvent(createNewEvent('click')); + + count += 1; + if (count < 5) { + simulateEvents(); + } + } + + setTimeout(simulateEvents, 500); + simulateEvents(); + }, selector); +} + +exports.makeNavAppear = makeNavAppear; +exports.navigateToNextItem = (I) => { + I.executeScript(function() { + window.preview.navigateRight(); + }); + I.waitForElement(CLASS_BOX_PREVIEW_LOADING_WRAPPER); +}; +exports.navigateToPrevItem = (I) => { + I.executeScript(function() { + window.preview.navigateLeft(); + }); + I.waitForElement(CLASS_BOX_PREVIEW_LOADING_WRAPPER); +}; +/* eslint-enable prefer-arrow-callback, no-var */ diff --git a/functional-tests/index.html b/functional-tests/index.html index d4f45e4a6..67a49af62 100644 --- a/functional-tests/index.html +++ b/functional-tests/index.html @@ -1,31 +1,35 @@ - - - - - - + + - - - + - var FILE_ID_DOC = '93392244621'; + + + + + + - var preview = new Box.Preview(); - preview.show(FILE_ID_DOC, ACCESS_TOKEN, { - container: '.preview-container', - showDownload: true, - }); - - diff --git a/functional-tests/sanity_test.js b/functional-tests/sanity_test.js index f975c3a28..028232055 100644 --- a/functional-tests/sanity_test.js +++ b/functional-tests/sanity_test.js @@ -5,7 +5,7 @@ Before((I) => { I.amOnPage('/functional-tests/index.html'); }); -Scenario('Sanity test @ci @mobile', (I) => { +Scenario('Sanity test @ci @chrome @firefox @edge @safari @ie @android @ios', (I) => { I.waitForElement(bpLoaded); I.waitForVisible(bpLoaded); I.waitForText('The Content Platform for Your Apps'); diff --git a/src/lib/Fullscreen.js b/src/lib/Fullscreen.js index 50c918124..7a4d01b35 100644 --- a/src/lib/Fullscreen.js +++ b/src/lib/Fullscreen.js @@ -28,7 +28,7 @@ class Fullscreen extends EventEmitter { } /** - * Binds DOM listeners for Fullscreen. + * Unbinds DOM listeners for Fullscreen. * * @protected * @return {void} diff --git a/src/lib/Preview.js b/src/lib/Preview.js index e0402f42e..bf1eaa650 100644 --- a/src/lib/Preview.js +++ b/src/lib/Preview.js @@ -826,6 +826,9 @@ class Preview extends EventEmitter { // (access stats will not be incremented), but content access is still logged server-side for audit purposes this.options.disableEventLog = !!options.disableEventLog; + // Options that are applicable to certain file ids + this.options.fileOptions = options.fileOptions || {}; + // Prefix any user created loaders before our default ones this.loaders = (options.loaders || []).concat(loaderList); diff --git a/src/lib/constants.js b/src/lib/constants.js index 735462f4a..92b6edd7a 100644 --- a/src/lib/constants.js +++ b/src/lib/constants.js @@ -102,3 +102,4 @@ export const TEXT_STATIC_ASSETS_VERSION = '0.114.0'; export const PREVIEW_SCRIPT_NAME = 'preview.js'; export const FILE_OPTION_FILE_VERSION_ID = 'fileVersionId'; +export const FILE_OPTION_START = 'startAt'; diff --git a/src/lib/polyfill.js b/src/lib/polyfill.js index 924ae38c5..970ea9235 100644 --- a/src/lib/polyfill.js +++ b/src/lib/polyfill.js @@ -210,4 +210,11 @@ if (!String.prototype.endsWith) { } }); } + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN +Number.isNaN = + Number.isNaN || + function(value) { + return value !== value; + }; /* eslint-enable */ diff --git a/src/lib/viewers/BaseViewer.js b/src/lib/viewers/BaseViewer.js index 9f2a20a8f..d03e270a2 100644 --- a/src/lib/viewers/BaseViewer.js +++ b/src/lib/viewers/BaseViewer.js @@ -20,6 +20,7 @@ import { CLASS_FULLSCREEN_UNSUPPORTED, CLASS_HIDDEN, CLASS_BOX_PREVIEW_MOBILE, + FILE_OPTION_START, SELECTOR_BOX_PREVIEW, SELECTOR_BOX_PREVIEW_BTN_ANNOTATE_POINT, SELECTOR_BOX_PREVIEW_BTN_ANNOTATE_DRAW, @@ -97,6 +98,9 @@ class BaseViewer extends EventEmitter { /** @property {boolean} - Whether viewer is being used on a touch device */ hasTouch; + /** @property {Object} - Viewer startAt options */ + startAt; + /** * [constructor] * @@ -108,6 +112,9 @@ class BaseViewer extends EventEmitter { this.options = options; this.cache = options.cache; this.previewUI = options.ui; + + this.startAt = getProp(options, `fileOptions.${options.file.id}.${FILE_OPTION_START}`, {}); + this.repStatuses = []; this.isMobile = Browser.isMobile(); this.hasTouch = Browser.hasTouch(); diff --git a/src/lib/viewers/doc/DocBaseViewer.js b/src/lib/viewers/doc/DocBaseViewer.js index c57e2291f..82d64257d 100644 --- a/src/lib/viewers/doc/DocBaseViewer.js +++ b/src/lib/viewers/doc/DocBaseViewer.js @@ -44,6 +44,7 @@ const MINIMUM_RANGE_REQUEST_FILE_SIZE_NON_US = 26214400; // 25MB const MOBILE_MAX_CANVAS_SIZE = 2949120; // ~3MP 1920x1536 const PINCH_PAGE_CLASS = 'pinch-page'; const PINCHING_CLASS = 'pinching'; +const PAGES_UNIT_NAME = 'pages'; class DocBaseViewer extends BaseViewer { //-------------------------------------------------------------------------- @@ -56,6 +57,8 @@ class DocBaseViewer extends BaseViewer { constructor(options) { super(options); + this.startPageNum = this.getStartPage(); + // Bind context for callbacks this.handleAssetAndRepLoad = this.handleAssetAndRepLoad.bind(this); this.print = this.print.bind(this); @@ -154,6 +157,34 @@ class DocBaseViewer extends BaseViewer { super.destroy(); } + /** + * Converts a value and unit to page number + * + * @return {number|undefined} a page number > 0 + */ + getStartPage() { + let convertedValue; + + const { unit, value } = this.startAt; + + if (!value || !unit) { + return convertedValue; + } + + if (unit === PAGES_UNIT_NAME) { + convertedValue = parseInt(value, 10); + + if (!convertedValue || convertedValue < 1) { + // Negative values aren't allowed, fall back to default behavior + return undefined; + } + } else { + console.error('Invalid unit for start:', unit); // eslint-disable-line no-console + } + + return convertedValue; + } + /** * Prefetches assets for a document. * @@ -366,11 +397,12 @@ class DocBaseViewer extends BaseViewer { * @return {void} */ setPage(pageNumber) { - if (!pageNumber || pageNumber < 1 || pageNumber > this.pdfViewer.pagesCount) { + const parsedPageNumber = parseInt(pageNumber, 10); + if (!parsedPageNumber || parsedPageNumber < 1 || parsedPageNumber > this.pdfViewer.pagesCount) { return; } - this.pdfViewer.currentPageNumber = pageNumber; + this.pdfViewer.currentPageNumber = parsedPageNumber; this.cachePage(this.pdfViewer.currentPageNumber); } @@ -862,8 +894,11 @@ class DocBaseViewer extends BaseViewer { this.loadUI(); - // Set current page to previously opened page or first page - this.setPage(this.getCachedPage()); + const { pagesCount, currentScale } = this.pdfViewer; + + // Set page to the user-defined page, previously opened page, or first page + const startPage = this.startPageNum || this.getCachedPage(); + this.setPage(startPage); // Make document scrollable after pages are set up so scrollbars don't mess with autoscaling this.docEl.classList.add(CLASS_IS_SCROLLABLE); @@ -872,9 +907,9 @@ class DocBaseViewer extends BaseViewer { if (!this.loaded) { this.loaded = true; this.emit(VIEWER_EVENT.load, { - numPages: this.pdfViewer.pagesCount, + numPages: pagesCount, endProgress: false, // Indicate that viewer will end progress later - scale: this.pdfViewer.currentScale + scale: currentScale }); // Add page IDs to each page after page structure is available diff --git a/src/lib/viewers/doc/__tests__/DocBaseViewer-test.js b/src/lib/viewers/doc/__tests__/DocBaseViewer-test.js index eb60c31aa..62c9a9bb8 100644 --- a/src/lib/viewers/doc/__tests__/DocBaseViewer-test.js +++ b/src/lib/viewers/doc/__tests__/DocBaseViewer-test.js @@ -30,6 +30,7 @@ const MAX_SCALE = 10.0; const MIN_SCALE = 0.1; const SCROLL_END_TIMEOUT = 500; const MOBILE_MAX_CANVAS_SIZE = 2949120; // ~3MP 1920x1536 +const PAGES_UNIT_NAME = 'pages'; const sandbox = sinon.sandbox.create(); let docBase; @@ -1285,6 +1286,18 @@ describe('src/lib/viewers/doc/DocBaseViewer', () => { }); expect(docBase.loaded).to.be.true; }); + + it('should set the start page based', () => { + const START_PAGE_NUM = 2; + const PAGES_COUNT = 3; + docBase.startPageNum = START_PAGE_NUM; + docBase.pdfViewer = { + pagesCount: PAGES_COUNT + }; + docBase.pagesinitHandler(); + + expect(stubs.setPage).to.have.been.calledWith(START_PAGE_NUM); + }); }); describe('pagerenderedHandler()', () => { @@ -1645,6 +1658,64 @@ describe('src/lib/viewers/doc/DocBaseViewer', () => { expect(docBase.pinchScale).to.equal(1); expect(docBase.pinchPage).to.equal(null); }); + }); + + describe('getStartPage()', () => { + it('should return the start page as a number', () => { + docBase.startAt = { + value : 3, + unit : PAGES_UNIT_NAME + }; + + expect(docBase.getStartPage()).to.equal(3); + }); + + it('should return the floored number if a floating point number is passed', () => { + docBase.startAt = { + value : 4.1, + unit : PAGES_UNIT_NAME + }; + + expect(docBase.getStartPage()).to.equal(4); + }); + it('should return undefined if a value < 1 is passed', () => { + docBase.startAt = { + value : 0, + unit : PAGES_UNIT_NAME + }; + + expect(docBase.getStartPage()).to.be.undefined; + + docBase.startAt = { + value : -100, + unit : PAGES_UNIT_NAME + }; + + expect(docBase.getStartPage()).to.be.undefined; + }); + + it('should return undefined if an invalid unit is passed', () => { + docBase.startAt = { + value : 3, + unit : 'foo' + }; + + expect(docBase.getStartPage()).to.be.undefined; + }); + + it('should return undefined if an invalid value is passed', () => { + docBase.startAt = { + value : 'foo', + unit : PAGES_UNIT_NAME + }; + + expect(docBase.getStartPage()).to.be.undefined; + }); + + it('should return undefined if no unit and value is passed', () => { + docBase.startAt = {}; + expect(docBase.getStartPage()).to.be.undefined; + }); }); }); diff --git a/src/lib/viewers/image/__tests__/ImageBaseViewer-test.js b/src/lib/viewers/image/__tests__/ImageBaseViewer-test.js index 3c90f068b..6b81dda0e 100644 --- a/src/lib/viewers/image/__tests__/ImageBaseViewer-test.js +++ b/src/lib/viewers/image/__tests__/ImageBaseViewer-test.js @@ -27,7 +27,11 @@ describe('lib/viewers/image/ImageBaseViewer', () => { fixture.load('viewers/image/__tests__/ImageBaseViewer-test.html'); stubs.emit = sandbox.stub(fullscreen, 'addListener'); containerEl = document.querySelector('.container'); - imageBase = new ImageBaseViewer(containerEl); + imageBase = new ImageBaseViewer({ + file: { + id: '1234' + } + }); imageBase.containerEl = containerEl; imageBase.imageEl = document.createElement('div'); @@ -233,13 +237,15 @@ describe('lib/viewers/image/ImageBaseViewer', () => { }; const promise = imageBase.setOriginalImageSize(imageEl); - promise.then(() => { - expect(imageEl.getAttribute('originalWidth')).to.equal(imageEl.naturalWidth); - expect(imageEl.getAttribute('originalHeight')).to.equal(imageEl.naturalHeight); - done(); - }).catch(() => { - Assert.fail(); - }); + promise + .then(() => { + expect(imageEl.getAttribute('originalWidth')).to.equal(imageEl.naturalWidth); + expect(imageEl.getAttribute('originalHeight')).to.equal(imageEl.naturalHeight); + done(); + }) + .catch(() => { + Assert.fail(); + }); }); it('should default to 300x150 when naturalHeight and naturalWidth are 0x0', (done) => { @@ -254,13 +260,15 @@ describe('lib/viewers/image/ImageBaseViewer', () => { const getStub = sandbox.stub(util, 'get').returns(Promise.resolve('not real a image')); const promise = imageBase.setOriginalImageSize(imageEl); - promise.then(() => { - expect(imageEl.getAttribute('originalWidth')).to.equal(300); - expect(imageEl.getAttribute('originalHeight')).to.equal(150); - done(); - }).catch(() => { - Assert.fail(); - }); + promise + .then(() => { + expect(imageEl.getAttribute('originalWidth')).to.equal(300); + expect(imageEl.getAttribute('originalHeight')).to.equal(150); + done(); + }) + .catch(() => { + Assert.fail(); + }); }); it('should resolve when the get call fails', (done) => { @@ -284,7 +292,12 @@ describe('lib/viewers/image/ImageBaseViewer', () => { 'bp-image-zoom-out-icon', ICON_ZOOM_OUT ); - expect(imageBase.controls.add).to.be.calledWith(__('zoom_in'), imageBase.zoomIn, 'bp-image-zoom-in-icon', ICON_ZOOM_IN); + expect(imageBase.controls.add).to.be.calledWith( + __('zoom_in'), + imageBase.zoomIn, + 'bp-image-zoom-in-icon', + ICON_ZOOM_IN + ); }); }); @@ -538,7 +551,10 @@ describe('lib/viewers/image/ImageBaseViewer', () => { it('should console log error and emit preview error', () => { const err = new Error('blah'); - sandbox.mock(window.console).expects('error').withArgs(err); + sandbox + .mock(window.console) + .expects('error') + .withArgs(err); imageBase.errorHandler(err); diff --git a/src/lib/viewers/media/DashViewer.js b/src/lib/viewers/media/DashViewer.js index bdd3a2878..01a458495 100644 --- a/src/lib/viewers/media/DashViewer.js +++ b/src/lib/viewers/media/DashViewer.js @@ -169,7 +169,7 @@ class DashViewer extends VideoBaseViewer { this.player.getNetworkingEngine().registerRequestFilter(this.requestFilter); this.startLoadTimer(); - this.player.load(this.mediaUrl); + this.player.load(this.mediaUrl, this.startTimeInSeconds); } /** diff --git a/src/lib/viewers/media/MediaBaseViewer.js b/src/lib/viewers/media/MediaBaseViewer.js index 056e95bf7..9e010cbc2 100644 --- a/src/lib/viewers/media/MediaBaseViewer.js +++ b/src/lib/viewers/media/MediaBaseViewer.js @@ -5,6 +5,7 @@ import MediaControls from './MediaControls'; import PreviewError from '../../PreviewError'; import { CLASS_ELEM_KEYBOARD_FOCUS, CLASS_HIDDEN, CLASS_IS_BUFFERING, CLASS_IS_VISIBLE } from '../../constants'; import { ERROR_CODE, VIEWER_EVENT } from '../../events'; +import { getProp } from '../../util'; const CSS_CLASS_MEDIA = 'bp-media'; const CSS_CLASS_MEDIA_CONTAINER = 'bp-media-container'; @@ -13,6 +14,8 @@ const MEDIA_VOLUME_CACHE_KEY = 'media-volume'; const MEDIA_AUTOPLAY_CACHE_KEY = 'media-autoplay'; const MEDIA_VOLUME_INCREMENT = 0.05; const EMIT_WAIT_TIME_IN_MILLIS = 100; +const SECONDS_UNIT_NAME = 'seconds'; +const INITIAL_TIME_IN_SECONDS = 0; class MediaBaseViewer extends BaseViewer { /** @@ -60,6 +63,39 @@ class MediaBaseViewer extends BaseViewer { this.loadTimeout = 100000; this.oldVolume = DEFAULT_VOLUME; this.pauseListener = null; + + this.startTimeInSeconds = this.getStartTimeInSeconds(); + } + + /** + * Converts a value and unit to seconds + * + * @param {string|number} value - the value e.g. 1 + * @param {string} unit - the unit e.g. seconds + * @return {number} a time in seconds + */ + getStartTimeInSeconds() { + let convertedValue = INITIAL_TIME_IN_SECONDS; + + const value = getProp(this.startAt, 'value'); + const unit = getProp(this.startAt, 'unit'); + + if (!value || !unit) { + return INITIAL_TIME_IN_SECONDS; + } + + if (unit === SECONDS_UNIT_NAME) { + convertedValue = parseFloat(value, 10); + + if (convertedValue < 0) { + // Negative values aren't allowed, start from beginning + return INITIAL_TIME_IN_SECONDS; + } + } else { + console.error('Invalid unit for start:', unit); // eslint-disable-line no-console + } + + return convertedValue; } /** @@ -159,6 +195,7 @@ class MediaBaseViewer extends BaseViewer { if (this.destroyed) { return; } + this.setMediaTime(this.startTimeInSeconds); this.handleVolume(); this.loaded = true; this.emit(VIEWER_EVENT.load); @@ -354,7 +391,9 @@ class MediaBaseViewer extends BaseViewer { * @return {void} */ setMediaTime(time) { - this.mediaEl.currentTime = time; + if (this.mediaEl && time >= 0 && time <= this.mediaEl.duration) { + this.mediaEl.currentTime = time; + } } /** diff --git a/src/lib/viewers/media/MediaControls.js b/src/lib/viewers/media/MediaControls.js index 2fbd5b41e..8cf8c8068 100644 --- a/src/lib/viewers/media/MediaControls.js +++ b/src/lib/viewers/media/MediaControls.js @@ -266,7 +266,7 @@ class MediaControls extends EventEmitter { 0, 1 ); - this.setTimeCode(0); // This also sets the aria values + this.setTimeCode(this.mediaEl.currentTime || 0); // This also sets the aria values this.timeScrubber.on('valuechange', () => { this.emit('timeupdate', this.getTimeFromScrubber()); }); diff --git a/src/lib/viewers/media/__tests__/DashViewer-test.js b/src/lib/viewers/media/__tests__/DashViewer-test.js index d09d68f0e..63f72cc0f 100644 --- a/src/lib/viewers/media/__tests__/DashViewer-test.js +++ b/src/lib/viewers/media/__tests__/DashViewer-test.js @@ -223,6 +223,16 @@ describe('lib/viewers/media/DashViewer', () => { expect(dash.startLoadTimer).to.be.called; }); + + it('should load the player with the start time', () => { + const START_TIME_IN_SECONDS = 3; + dash.mediaUrl = 'url'; + dash.startTimeInSeconds = START_TIME_IN_SECONDS; + sandbox.stub(shaka, 'Player').returns(dash.player); + stubs.mockPlayer.expects('load').withArgs('url', START_TIME_IN_SECONDS); + + dash.loadDashPlayer(); + }) }); describe('requestFilter()', () => { diff --git a/src/lib/viewers/media/__tests__/MediaBaseViewer-test.js b/src/lib/viewers/media/__tests__/MediaBaseViewer-test.js index 4d4599d19..237cecf6e 100644 --- a/src/lib/viewers/media/__tests__/MediaBaseViewer-test.js +++ b/src/lib/viewers/media/__tests__/MediaBaseViewer-test.js @@ -332,6 +332,7 @@ describe('lib/viewers/media/MediaBaseViewer', () => { describe('setMediaTime()', () => { it('should set the time on the media element', () => { media.mediaEl = document.createElement('video'); + media.mediaEl.duration = 4; const newTime = 3.14; media.setMediaTime(newTime);