Skip to content

Commit

Permalink
feat(FEC-10632): handle player dimensions dynamically (#506)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dan Ziv committed Nov 23, 2020
1 parent 4116487 commit 9cbf0cf
Show file tree
Hide file tree
Showing 5 changed files with 266 additions and 4 deletions.
52 changes: 51 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ var player = playkit.core.loadPlayer(config);
network: PKNetworkConfigObject,
customLabels: PKCustomLabelsConfigObject,
abr: PKAbrConfigObject,
drm: PKDrmConfigObject
drm: PKDrmConfigObject,
dimensions: PKDimensionsConfig
}
```

Expand Down Expand Up @@ -1386,4 +1387,53 @@ var config = {
##
> ### config.dimensions
>
> ##### Type: `PKDimensionsConfig`
>
> ```js
> {
> width?: string | number;
> height?: string | number;
> ratio?: string;
> }
> ```
>
> ##### Description: Dimensions configuration
>
> > ### config.dimensions.width
> >
> > ##### Type: `string | number`
> >
> > ##### Default: `''`
> >
> > ##### Description: The width of the player.
> >
> > If number was provided, the width will be calculated in pixels (`width: 640` equivalent to `width: '640px'`).
> > If string was provided, any valid css syntax can be passed, for example: `width: '100%'`, `width: 'auto'`, etc.
> >
> > ### config.dimensions.height
> >
> > ##### Type: `string | number`
> >
> > ##### Default: `''`
> >
> > ##### Description: The height of the player.
> >
> > If number was provided, the height will be calculated in pixels (`height: 360` equivalent to `width: '360px'`).
> > If string was provided, any valid css syntax can be passed, for example: `height: '100%'`, `height: 'auto'`, etc.
> >
> > ### config.dimensions.ratio
> >
> > ##### Type: `string`
> >
> > ##### Default: `''`
> >
> > ##### Description: Defines the aspect ratio of the player.
> >
> > The aspect ratio should be written in the form of `'width:height'`, for example: `'4:3'` (classic TV ratio).
> > If one of the `height` or `width` parameters is additionally provided in the configuration, the value of the other parameter not provided will be calculated accordingly to match the aspect ratio. If both were provided, the `height` value would be overridden.
##
Now that we've learned about the different options available in the player configuration, let's see [how does the source selection logic works](./source-selection-logic.md).
10 changes: 10 additions & 0 deletions flow-typed/types/dimensions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
declare type PKPlayerDimensions = {
width: number,
height: number
};

declare type PKDimensionsConfig = {
width?: number | string,
height?: number | string,
ratio?: string
};
67 changes: 65 additions & 2 deletions src/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,12 @@ export default class Player extends FakeEventTarget {
* @private
*/
_shouldLoadAfterAttach: boolean = false;
/**
* The aspect ratio of the player.
* @type {?string}
* @private
*/
_aspectRatio: ?string;

/**
* @param {Object} config - The configuration for the player instance.
Expand Down Expand Up @@ -454,6 +460,7 @@ export default class Player extends FakeEventTarget {
this._attachMedia();
this._handlePlaybackOptions();
this._posterManager.setSrc(this._config.sources.poster);
this._handleDimensions();
this._handlePreload();
this._handleAutoPlay();
Player._logger.debug('Change source ended');
Expand Down Expand Up @@ -881,12 +888,32 @@ export default class Player extends FakeEventTarget {
return null;
}

/**
* Sets the dimensions of the player.
* @param {PKDimensionsConfig} dimensions - the player dimensions config.
* @returns {void}
* @public
*/
set dimensions(dimensions?: PKDimensionsConfig) {
const targetElement = document.getElementById(this.config.targetId);
if (!dimensions || Utils.Object.isEmptyObject(dimensions)) {
this._aspectRatio = null;
targetElement.style.width = null;
targetElement.style.height = null;
} else {
const {height, width} = Utils.Object.mergeDeep(this.dimensions, dimensions);
targetElement.style.width = typeof width === 'number' ? `${width}px` : width;
targetElement.style.height = typeof height === 'number' ? `${height}px` : height;
this._calcRatio(targetElement, dimensions);
}
}

/**
* Get the dimensions of the player.
* @returns {{width: number, height: number}} - The dimensions of the player.
* @returns {PKPlayerDimensions} - The dimensions of the player.
* @public
*/
get dimensions(): Object {
get dimensions(): PKPlayerDimensions {
return {
width: this._el.clientWidth,
height: this._el.clientHeight
Expand Down Expand Up @@ -1914,6 +1941,18 @@ export default class Player extends FakeEventTarget {
}
}

/**
* Handles and sets the initial dimensions configuration if such exists.
* @private
* @returns {void}
*/
_handleDimensions(): void {
const {dimensions} = this.config;
if (Utils.Object.isObject(dimensions) && !Utils.Object.isEmptyObject(dimensions)) {
this.dimensions = dimensions;
}
}

/**
* Start/resume the engine playback.
* @private
Expand Down Expand Up @@ -2010,6 +2049,7 @@ export default class Player extends FakeEventTarget {
this._pause();
}
}

/**
* Resets the state flags of the player.
* @returns {void}
Expand All @@ -2023,6 +2063,29 @@ export default class Player extends FakeEventTarget {
this._firstPlaying = false;
}

/**
* Calculates the aspect ratio of the player.
* @param {HTMLDivElement} targetElement - the player root element.
* @param {PKDimensionsConfig} dimensions - the player dimensions input.
* @returns {void}
* @public
*/
_calcRatio(targetElement: HTMLDivElement, dimensions: PKDimensionsConfig) {
if (typeof dimensions.ratio !== 'undefined') {
this._aspectRatio = dimensions.ratio;
}
if (this._aspectRatio) {
const [ratioWidth, ratioHeight] = this._aspectRatio.split(':').map(r => Number(r));
if (dimensions.width || (!dimensions.width && !dimensions.height)) {
const height = (ratioHeight / ratioWidth) * targetElement.clientWidth;
targetElement.style.height = `${height}px`;
} else if (dimensions.height && !dimensions.width) {
const width = (ratioWidth / ratioHeight) * targetElement.clientHeight;
targetElement.style.width = `${width}px`;
}
}
}

/**
* @returns {Object} - The default configuration of the player.
* @private
Expand Down
138 changes: 138 additions & 0 deletions test/src/dimensions.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import {createElement, getConfigStructure, removeElement, removeVideoElementsFromTestPage} from './utils/test-utils';
import Player from '../../src/player';
import {Object as PKObject} from '../../src/utils';
import SourcesConfig from './configs/sources.json';

const targetId = 'player-placeholder_dimensions.spec';
const sourcesConfig = PKObject.copyDeep(SourcesConfig);

describe('Dimensions API ', function () {
const origConfig = getConfigStructure(targetId);
const playerContainer = createElement('DIV', targetId);
let player, config;

before(() => {
origConfig.sources = sourcesConfig.MultipleSources;
});

const createPlayer = config => {
player = new Player(config);
playerContainer.appendChild(player.getView());
};

beforeEach(() => {
config = PKObject.copyDeep(origConfig);
});

afterEach(() => {
player.destroy();
});

after(() => {
removeVideoElementsFromTestPage();
removeElement(targetId);
});

it('should calc height to match configure ratio', () => {
config.dimensions = {
ratio: '4:3',
width: 100
};
createPlayer(config);
player.dimensions.should.deep.equals({width: 100, height: 75});
});

it('should calc width to match configure ratio', () => {
config.dimensions = {
ratio: '4:3',
height: 75
};
createPlayer(config);
player.dimensions.should.deep.equals({width: 100, height: 75});
});

it('should calc width and override height to match configure ratio', () => {
config.dimensions = {
ratio: '4:3',
width: 100,
height: 100
};
createPlayer(config);
player.dimensions.should.deep.equals({width: 100, height: 75});
});

it('should set the configure width and height', () => {
config.dimensions = {
width: 100,
height: 100
};
createPlayer(config);
player.dimensions.should.deep.equals({width: 100, height: 100});
});

it('should dynamically change the width and height', () => {
config.dimensions = {
width: 100,
height: 100
};
createPlayer(config);
player.dimensions.should.deep.equals({width: 100, height: 100});
player.dimensions = {height: 50, width: 70};
player.dimensions.should.deep.equals({height: 50, width: 70});
player.dimensions = {height: 60};
player.dimensions.should.deep.equals({height: 60, width: 70});
player.dimensions = {width: 200};
player.dimensions.should.deep.equals({height: 60, width: 200});
});

it('should reset width and height', done => {
config.dimensions = {
width: 100,
height: 100
};
createPlayer(config);
player.play();
player.ready().then(() => {
player.dimensions.should.deep.equals({width: 100, height: 100});
player.dimensions = {};
player.dimensions.width.should.equals(window.innerWidth);
player.dimensions = {width: 90, height: 80};
player.dimensions.should.deep.equals({width: 90, height: 80});
player.dimensions = null;
player.dimensions.width.should.equals(window.innerWidth);
done();
});
});

it('should change ratio dynamically', () => {
config.dimensions = {
ratio: '4:3',
width: 100
};
createPlayer(config);
player.dimensions.should.deep.equals({width: 100, height: 75});
player.dimensions = {ratio: '9:2'};
player.dimensions.should.deep.equals({width: 100, height: 22});
player.dimensions = {height: 100};
player.dimensions.should.deep.equals({width: 450, height: 100});
});

it('should reset ratio dynamically', () => {
config.dimensions = {
ratio: '4:3',
width: 100
};
createPlayer(config);
player.dimensions.should.deep.equals({width: 100, height: 75});
player.dimensions = {ratio: ''};
player.dimensions.should.deep.equals({width: 100, height: 75});
player.dimensions = {height: 100};
player.dimensions.should.deep.equals({width: 100, height: 100});
player.dimensions = {ratio: '4:3'};
player.dimensions.should.deep.equals({width: 100, height: 75});
player.dimensions = {ratio: null};
player.dimensions.should.deep.equals({width: 100, height: 75});
player.dimensions = {height: 100};
player.dimensions.should.deep.equals({width: 100, height: 100});
});
});
3 changes: 2 additions & 1 deletion test/src/utils/test-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
* Configuration structure of the player.
* @returns {Object} - The configuration structure of the player.
*/
function getConfigStructure() {
function getConfigStructure(targetId) {
return {
targetId,
sources: {},
playback: {
enableCEA708Captions: true,
Expand Down

0 comments on commit 9cbf0cf

Please sign in to comment.