Skip to content

Commit

Permalink
feat(videodetail): player render optimizations
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristiaanScheermeijer committed Jun 21, 2021
1 parent 23d0284 commit eefa987
Show file tree
Hide file tree
Showing 11 changed files with 274 additions and 150 deletions.
39 changes: 23 additions & 16 deletions src/components/Video/Video.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,50 +9,55 @@
text-shadow: 0px 2px 4px rgba(0, 0, 0, 0.14), 0px 3px 4px rgba(0, 0, 0, 0.12), 0px 1px 5px rgba(0, 0, 0, 0.2);
}
.mainPadding {
padding: 56px;
@include responsive.tablet-only() {
padding: 24px;
padding-top: 34px;
padding: 34px 24px 24px;
}

@include responsive.mobile-only() {
padding: 16px;
}
padding-top: 37px;
padding-bottom: 0px;

padding: 37px 56px 0;
}
.main {
display: flex;

&.hidden {
display: none;
}

&.posterNormal {
position: relative;
}
}

.info {
width: 450px;
min-height: 380px;
@include responsive.tablet-only() {
width: 350px;
}

@include responsive.mobile-only() {
width: 100%;
padding-top: 225px;
}

width: 450px;
min-height: 380px;
}

.title {
font-size: 34px;
font-weight: 700;
line-height: 36px;
letter-spacing: 0.25px;
margin: 8px 0px;
@include responsive.mobile-only() {
font-size: 20px;
line-height: 20px;
letter-spacing: 0.15px;
margin: 0px;
}

font-weight: 700;
font-size: 34px;
line-height: 40px;
letter-spacing: 0.25px;
}

.metaContainer {
display: flex;
flex-direction: column;
Expand Down Expand Up @@ -202,7 +207,7 @@
.playerContent {
position: absolute;
top: 0;
margin: 30px 62px;
margin: 24px 56px;
max-width: 50%;
display: flex;

Expand All @@ -212,16 +217,18 @@
}
}
.backButton {
margin: 6px 0px;
margin-right: 24px;
opacity: 1;

> svg {
width: 36px;
height: 36px;
}
}

.playerInfo {
margin: 0px 30px;
}

.trailerMeta {
position: absolute;
z-index: 1;
Expand Down
27 changes: 17 additions & 10 deletions src/components/Video/Video.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useCallback } from 'react';
import type { PlaylistItem } from 'types/playlist';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
Expand Down Expand Up @@ -69,6 +69,13 @@ const Video: React.FC<Props> = ({
const [userActive, setUserActive] = useState(true);
const breakpoint: Breakpoint = useBreakpoint();
const { t } = useTranslation(['video', 'common']);

const handleUserActive = useCallback(() => setUserActive(true), []);
const handleUserInactive = useCallback(() => setUserActive(false), []);
const handlePlay = useCallback(() => setIsPlaying(true), []);
const handlePause = useCallback(() => setIsPlaying(false), []);
const handleComplete = useCallback(() => onComplete && onComplete(), [onComplete]);

const isLargeScreen = breakpoint >= Breakpoint.md;
const isMobile = breakpoint === Breakpoint.xs;
const imageSourceWidth = 640 * (window.devicePixelRatio > 1 || isLargeScreen ? 2 : 1);
Expand Down Expand Up @@ -160,11 +167,11 @@ const Video: React.FC<Props> = ({
<Cinema
item={item}
feedId={feedId}
onPlay={() => setIsPlaying(true)}
onPause={() => setIsPlaying(false)}
onComplete={onComplete}
onUserActive={() => setUserActive(true)}
onUserInActive={() => setUserActive(false)}
onPlay={handlePlay}
onPause={handlePause}
onComplete={handleComplete}
onUserActive={handleUserActive}
onUserInActive={handleUserInactive}
/>
<div className={classNames(styles.playerOverlay, { [styles.hidden]: isPlaying && !userActive })} />
</div>
Expand All @@ -186,11 +193,11 @@ const Video: React.FC<Props> = ({
<Modal onClose={onTrailerClose} closeButtonVisible={!isPlaying || userActive}>
<Cinema
item={trailerItem}
onPlay={() => setIsPlaying(true)}
onPause={() => setIsPlaying(false)}
onPlay={handlePlay}
onPause={handlePause}
onComplete={onTrailerClose}
onUserActive={() => setUserActive(true)}
onUserInActive={() => setUserActive(false)}
onUserActive={handleUserActive}
onUserInActive={handleUserInactive}
isTrailer
/>
<div className={classNames(styles.playerOverlay, { [styles.hidden]: isPlaying && !userActive })} />
Expand Down
10 changes: 5 additions & 5 deletions src/components/Video/__snapshots__/Video.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,10 @@ exports[`<Video> renders and matches snapshot 1`] = `
class="player"
>
<div
id="cinema"
/>
class="cinema"
>
<div />
</div>
<div
class="playerOverlay"
/>
Expand Down Expand Up @@ -158,9 +160,7 @@ exports[`<Video> renders and matches snapshot 1`] = `
</g>
</svg>
</div>
<div
class="playerInfo"
>
<div>
<h2
class="title"
>
Expand Down
5 changes: 5 additions & 0 deletions src/containers/Cinema/Cinema.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.cinema {
display: flex;
width: 100%;
height: 100%;
}
144 changes: 113 additions & 31 deletions src/containers/Cinema/Cinema.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useContext, useEffect, useState } from 'react';
import React, { useContext, useEffect, useRef } from 'react';
import type { Config } from 'types/Config';
import type { PlaylistItem } from 'types/playlist';
import type { VideoProgress } from 'types/video';
Expand All @@ -11,6 +11,8 @@ import { ConfigContext } from '../../providers/ConfigProvider';
import { addScript } from '../../utils/dom';
import useOttAnalytics from '../../hooks/useOttAnalytics';

import styles from './Cinema.module.scss';

type Props = {
item: PlaylistItem;
onPlay?: () => void;
Expand All @@ -22,59 +24,139 @@ type Props = {
isTrailer?: boolean;
};

const Cinema: React.FC<Props> = ({ item, onPlay, onPause, onComplete, onUserActive, onUserInActive, feedId, isTrailer = false }: Props) => {
const Cinema: React.FC<Props> = (
{
item,
onPlay,
onPause,
onComplete,
onUserActive,
onUserInActive,
feedId,
isTrailer = false,
}: Props,
) => {
const config: Config = useContext(ConfigContext);
const [initialized, setInitialized] = useState(false);
const file = item.sources?.[0]?.file;
const playerElementRef = useRef<HTMLDivElement>(null);
const playerRef = useRef<JWPlayer>();
const scriptUrl = `https://content.jwplatform.com/libraries/${config.player}.js`;
const enableWatchHistory = config.options.enableContinueWatching && !isTrailer;
const setPlayer = useOttAnalytics(item, feedId);

const getProgress = (): VideoProgress | null => {
const player = window.jwplayer && (window.jwplayer('cinema') as JWPlayer);
if (!player) return null;
if (!playerRef.current) return null;

const duration = player.getDuration();
const progress = player.getPosition() / duration;
const duration = playerRef.current.getDuration();
const progress = playerRef.current.getPosition() / duration;

return { duration, progress } as VideoProgress;
};

const { saveItem } = useWatchHistory();
useWatchHistoryListener(() => (enableWatchHistory ? saveItem(item, getProgress) : null));

useEffect(() => {
const getPlayer = () => window.jwplayer && (window.jwplayer('cinema') as JWPlayer);
const loadVideo = () => {
const player = getPlayer();
if (!config.player) {
return;
}

const loadPlaylist = () => {
if (!item || !playerRef.current) {
return;
}

const currentItem = playerRef.current?.getPlaylistItem() as PlaylistItem | null;

// we already loaded this item
if (currentItem && currentItem.mediaid === item.mediaid) {
return;
}

// load new item
playerRef.current.load([{
mediaid: item.mediaid,
image: item.image,
title: item.title,
description: item.description,
sources: item.sources.map(source => ({ ...source })),
}]);
};

const initializePlayer = () => {
if (!window.jwplayer || !playerElementRef.current) return;

const { watchHistory } = watchHistoryStore.getRawState();
const watchHistoryItem = watchHistory.find(({ mediaid }) => mediaid === item.mediaid);
let applyWatchHistory = !!watchHistory && enableWatchHistory;

player.setup({ file, image: item.image, title: item.title, autostart: 'viewable' });
setPlayer(player);
player.on('play', () => onPlay && onPlay());
player.on('pause', () => onPause && onPause());
player.on('beforePlay', () => {
if (applyWatchHistory) {
applyWatchHistory = false; // Only the first time beforePlay
const { progress, duration } = watchHistoryItem || {};
if (progress && duration && progress > VideoProgressMinMax.Min && progress < VideoProgressMinMax.Max) {
player.seek(duration * progress);
}
}
playerRef.current = window.jwplayer(playerElementRef.current);

playerRef.current.setup({
playlist: [{
mediaid: item.mediaid,
image: item.image,
title: item.title,
description: item.description,
sources: item.sources.map(source => ({ ...source })),
}],
aspect: false,
width: '100%',
height: '100%',
mute: false,
autostart: true,
});
player.on('complete', () => onComplete && onComplete());
player.on('userActive', () => onUserActive && onUserActive());
player.on('userInactive', () => onUserInActive && onUserInActive());

setPlayer(playerRef.current);

const handlePlay = () => onPlay && onPlay();
const handlePause = () => onPause && onPause();
const handleComplete = () => onComplete && onComplete();
const handleUserActive = () => onUserActive && onUserActive();
const handleUserInactive = () => onUserInActive && onUserInActive();

const handleBeforePlay = () => {
if (!applyWatchHistory) {
return;
}

applyWatchHistory = false; // Only the first time beforePlay
const { progress, duration } = watchHistoryItem || {};

if (playerRef.current && progress && duration && progress > VideoProgressMinMax.Min && progress < VideoProgressMinMax.Max) {
playerRef.current.seek(duration * progress);
}
};

playerRef.current.on('play', handlePlay);
playerRef.current.on('pause', handlePause);
playerRef.current.on('complete', handleComplete);

playerRef.current.on('beforePlay', handleBeforePlay);

playerRef.current.on('userActive', handleUserActive);
playerRef.current.on('userInactive', handleUserInactive);
};

if (config.player && !initialized) {
getPlayer() ? loadVideo() : addScript(scriptUrl, loadVideo);
setInitialized(true);
if (playerRef.current) {
return loadPlaylist();
}
}, [item, onPlay, onPause, onComplete, onUserActive, onUserInActive, config.player, file, scriptUrl, initialized, enableWatchHistory, setPlayer]);

return <div id="cinema" />;
window.jwplayer ? initializePlayer() : addScript(scriptUrl, initializePlayer);
}, [item, onPlay, onPause, onUserActive, onUserInActive, onComplete, config.player, scriptUrl, enableWatchHistory, setPlayer]);

useEffect(() => {
return () => {
if (playerRef.current) {
playerRef.current.remove();
}
};
}, []);

return (
<div className={styles.cinema}>
<div ref={playerElementRef} />
</div>
);
};

export default Cinema;
6 changes: 4 additions & 2 deletions src/containers/Cinema/__snapshots__/Cinema.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
exports[`<Cinema> renders and matches snapshot 1`] = `
<div>
<div
id="cinema"
/>
class="cinema"
>
<div />
</div>
</div>
`;
Loading

0 comments on commit eefa987

Please sign in to comment.