Skip to content

Commit

Permalink
feat(FEC-8277): Add picture-in-picture support (#283)
Browse files Browse the repository at this point in the history
* picture-in-picture

* picture in picture support

+ player API
+ new icon

* fixing in issue in mobile safari.

webkitpresentationmode is updated after 'loadedmetadata' event is raised. Checking this after loadedmetadata event is updated.

* removing redundant code

* merge

* moving this into the player class

* removing redundant size
  • Loading branch information
odedhutzler committed Nov 4, 2018
1 parent 0c0133e commit 687426c
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 5 deletions.
1 change: 1 addition & 0 deletions src/components/engine-connector/engine-connector.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ class EngineConnector extends BaseComponent {
this.props.updateIsLive(this.player.isLive());
this.props.updateIsDvr(this.player.isDvr());
this.props.updatePlayerPoster(this.player.poster);
this.props.updatePictureInPictureSupport(this.player.isPictureInPictureSupported());
});

this.eventManager.listen(this.player, this.player.Event.VOLUME_CHANGE, () => {
Expand Down
6 changes: 5 additions & 1 deletion src/components/icon/icon.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ const IconType = {
Next: 'next',
NextDisabled: 'next-disabled',
Prev: 'prev',
PrevDisabled: 'prev-disabled'
PrevDisabled: 'prev-disabled',
PictureInPicture: 'picture-in-picture'
};

/**
Expand Down Expand Up @@ -186,6 +187,9 @@ class Icon extends Component {
case IconType.PrevDisabled:
return <i className={[style.icon, style.iconPrevDisabled].join(' ')} />;

case IconType.PictureInPicture:
return <i className={[style.icon, style.iconPictureInPicture].join(' ')} />;

default:
break;
}
Expand Down
10 changes: 8 additions & 2 deletions src/components/icon/icon.scss
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,10 @@
next:
'<path fill="#{$color}" d="M640 549.333l-264.982 154.573c-30.386 17.725-55.018 3.388-55.018-32.094v-319.625c0-35.45 24.605-49.835 55.018-32.094l264.982 154.573v-154.448c0-17.794 14.204-32.219 32-32.219 17.673 0 32 14.398 32 32.219v383.562c0 17.794-14.204 32.219-32 32.219-17.673 0-32-14.398-32-32.219v-154.448z"></path>',
prev:
'<path fill="#{$color}" d="M384 549.333l264.982 154.573c30.386 17.725 55.018 3.388 55.018-32.094v-319.625c0-35.45-24.605-49.835-55.018-32.094l-264.982 154.573v-154.448c0-17.794-14.204-32.219-32-32.219-17.673 0-32 14.398-32 32.219v383.562c0 17.794 14.204 32.219 32 32.219 17.673 0 32-14.398 32-32.219v-154.448z"></path>'
);
'<path fill="#{$color}" d="M384 549.333l264.982 154.573c30.386 17.725 55.018 3.388 55.018-32.094v-319.625c0-35.45-24.605-49.835-55.018-32.094l-264.982 154.573v-154.448c0-17.794-14.204-32.219-32-32.219-17.673 0-32 14.398-32 32.219v383.562c0 17.794 14.204 32.219 32 32.219 17.673 0 32-14.398 32-32.219v-154.448z"></path>',
pictureInPicture:
'<path fill="#{$color}" d="M224 256c-17.673 0-32 14.327-32 32v448c0 17.673 14.327 32 32 32h576c17.673 0 32-14.327 32-32v-448c0-17.673-14.327-32-32-32h-576zM224 192h576c53.019 0 96 42.981 96 96v448c0 53.019-42.981 96-96 96h-576c-53.019 0-96-42.981-96-96v-448c0-53.019 42.981-96 96-96z"></path><path fill="#{$color}" d="M544 512h192c17.673 0 32 14.327 32 32v128c0 17.673-14.327 32-32 32h-192c-17.673 0-32-14.327-32-32v-128c0-17.673 14.327-32 32-32z"></path>'
);
$icon: map-get($icons, $icon-name);
$svg-encoded-icon: svg-url($icon);
@return $svg-encoded-icon;
Expand Down Expand Up @@ -291,3 +293,7 @@
.icon-prev-disabled {
background-image: icon(prev, 'graytext')
}

.icon-picture-in-picture {
background-image: icon(pictureInPicture, '#fff')
}
1 change: 1 addition & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ export {CastOverlay} from './cast-overlay';
export {VrStereoToggleControl} from './vr-stereo-toggle';
export {CastBeforePlay, CastAfterPlay} from './cast-on-tv';
export {PlaylistButton} from './playlist-button';
export {PictureInPicture} from './picture-in-picture';
5 changes: 5 additions & 0 deletions src/components/picture-in-picture/_picture-in-picture.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.player{
.picture-in-picture{

}
}
1 change: 1 addition & 0 deletions src/components/picture-in-picture/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {PictureInPicture} from './picture-in-picture';
79 changes: 79 additions & 0 deletions src/components/picture-in-picture/picture-in-picture.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//@flow
import style from '../../styles/style.scss';
import {h} from 'preact';
import {Localizer, Text} from 'preact-i18n';
import {default as Icon, IconType} from '../icon';
import {KeyMap} from '../../utils/key-map';
import BaseComponent from '../base';
import {connect} from 'preact-redux';

/**
* mapping state to props
* @param {*} state - redux store state
* @returns {Object} - mapped state to this component
*/
const mapStateToProps = state => ({
isPictureInPictureSupported: state.engine.isPictureInPictureSupported
});

@connect(mapStateToProps)
/**
* PictureInPicture component
*
* @class PictureInPicture
* @extends {BaseComponent}
*/
class PictureInPicture extends BaseComponent {
/**
* Creates an instance of PictureInPicture.
* @param {Object} obj - the object passed when created
* @memberof PictureInPicture
*/
constructor(obj: Object) {
super({name: 'PictureInPicture', player: obj.player});
}

/**
* On PIP icon clicked
* @returns {void}
* @private
*/
_onClick(): void {
if (this.player.isInPictureInPicture()) {
this.player.exitPictureInPicture();
} else {
this.player.enterPictureInPicture();
}
}

/**
* render component
*
* @returns {React$Element} - component element
* @memberof RewindControl
*/
render(): React$Element<any> | void {
if (this.props.isPictureInPictureSupported) {
return (
<div className={[style.controlButtonContainer, style.pictureInPicture].join(' ')}>
<Localizer>
<button
tabIndex="0"
aria-label={<Text id={'controls.PictureInPicture'} />}
className={`${style.controlButton} ${this.state.animation ? style.rotate : ''}`}
onClick={() => this._onClick()}
onKeyDown={e => {
if (e.keyCode === KeyMap.ENTER) {
this._onClick();
}
}}>
<Icon type={IconType.PictureInPicture} />
</button>
</Localizer>
</div>
);
}
}
}

export {PictureInPicture};
16 changes: 14 additions & 2 deletions src/reducers/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ export const types = {
UPDATE_IS_CASTING: `${component}/UPDATE_IS_CASTING`,
UPDATE_CAST_SESSION: `${component}/UPDATE_CAST_SESSION`,
UPDATE_IS_CAST_AVAILABLE: `${component}/UPDATE_IS_CAST_AVAILABLE`,
UPDATE_PLAYLIST: `${component}/UPDATE_PLAYLIST`
UPDATE_PLAYLIST: `${component}/UPDATE_PLAYLIST`,
UPDATE_PICTURE_IN_PICTURE_SUPPORTED: `${component}/UPDATE_PICTURE_IN_PICTURE_SUPPORTED`
};

export const initialState = {
Expand Down Expand Up @@ -78,6 +79,7 @@ export const initialState = {
isCasting: false,
castSession: null,
isCastAvailable: false,
pictureInPictureSupported: false,
playlist: null
};

Expand Down Expand Up @@ -290,6 +292,12 @@ export default (state: Object = initialState, action: Object) => {
playlist: action.playlist
};

case types.UPDATE_PICTURE_IN_PICTURE_SUPPORTED:
return {
...state,
isPictureInPictureSupported: action.isPictureInPictureSupported
};

default:
return state;
}
Expand Down Expand Up @@ -340,5 +348,9 @@ export const actions = {
type: types.UPDATE_IS_CHANGING_SOURCE,
isChangingSource
}),
updatePlaylist: (playlist: Object) => ({type: types.UPDATE_PLAYLIST, playlist})
updatePlaylist: (playlist: Object) => ({type: types.UPDATE_PLAYLIST, playlist}),
updatePictureInPictureSupport: (isPictureInPictureSupported: boolean) => ({
type: types.UPDATE_PICTURE_IN_PICTURE_SUPPORTED,
isPictureInPictureSupported
})
};
1 change: 1 addition & 0 deletions src/styles/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@
@import '../components/cast-on-tv/cast-on-tv';
@import '../components/backdrop/backdrop';
@import '../components/playlist-button/playlist-button';
@import '../components/picture-in-picture/picture-in-picture';
2 changes: 2 additions & 0 deletions src/ui-presets/playback.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {CastControl} from '../components/cast';
import {CastBeforePlay} from '../components/cast-on-tv/cast-before-play';
import {Backdrop} from '../components/backdrop/backdrop';
import {PlaylistButton} from '../components/playlist-button/playlist-button';
import {PictureInPicture} from '../components/picture-in-picture';

/**
* Playback ui interface
Expand Down Expand Up @@ -59,6 +60,7 @@ export function playbackUI(props: any): React$Element<any> {
<LanguageControl player={props.player} />
<SettingsControl player={props.player} />
<CastControl player={props.player} />
<PictureInPicture player={props.player} />
<FullscreenControl player={props.player} />
</div>
</BottomBar>
Expand Down

0 comments on commit 687426c

Please sign in to comment.