Skip to content
This repository has been archived by the owner on Aug 13, 2023. It is now read-only.

Add AV embed timeout handling to Media Player #2836

Merged
merged 55 commits into from
Jan 15, 2020
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
a05bd2d
Bump package version
simonsinclair Dec 20, 2019
449938b
Add changelog entry
simonsinclair Dec 20, 2019
8b76e8e
Merge branch 'latest' of https://github.com/bbc/psammead into media-p…
simonsinclair Dec 23, 2019
8e85f35
Merge branch 'latest' of https://github.com/bbc/psammead into media-p…
simonsinclair Dec 23, 2019
338933f
Add useTimeout hook
simonsinclair Dec 23, 2019
44b87f6
Refactor Canonical to use timeout hook
simonsinclair Dec 23, 2019
50aa239
Update snapshots
simonsinclair Dec 23, 2019
cc265d0
Merge branch 'latest' of https://github.com/bbc/psammead into media-p…
simonsinclair Dec 23, 2019
a047835
Prune former effect dependency
simonsinclair Dec 23, 2019
8d31be2
Merge branch 'media-player-timeout' of https://github.com/bbc/psammea…
simonsinclair Jan 6, 2020
c8dee83
Move useTimeout hook to Canonical
simonsinclair Jan 6, 2020
25166f9
Bump TIMEOUT_MS to '5000'
simonsinclair Jan 6, 2020
0f592b0
Rename 'iframe' reference to 'iframeRef'
simonsinclair Jan 6, 2020
5e02b82
Housekeeping
simonsinclair Jan 6, 2020
8362ee4
Add JSDoc
simonsinclair Jan 6, 2020
326e938
Merge branch 'latest' of https://github.com/bbc/psammead into media-p…
simonsinclair Jan 6, 2020
6dfa9a8
Merge branch 'latest' of https://github.com/bbc/psammead into media-p…
simonsinclair Jan 7, 2020
fb9da72
Merge branch 'media-player-timeout' of https://github.com/bbc/psammea…
simonsinclair Jan 7, 2020
49fe6bb
Remove hook dependency on 'timeout'
simonsinclair Jan 7, 2020
310ebcd
Expand on hook comments
simonsinclair Jan 7, 2020
423ee27
Merge branch 'latest' into media-player-timeout
simonsinclair Jan 7, 2020
65169f9
Merge branch 'latest' of https://github.com/bbc/psammead into media-p…
simonsinclair Jan 7, 2020
66bd1b1
Merge branch 'media-player-timeout' of https://github.com/bbc/psammea…
simonsinclair Jan 7, 2020
76e8e44
Merge branch 'latest' of https://github.com/bbc/psammead into media-p…
simonsinclair Jan 8, 2020
b9bccce
Replace 'noJsClassName' with actual required prop
simonsinclair Jan 8, 2020
deca192
Update JSDoc comment on 'iframeRef'
simonsinclair Jan 8, 2020
9a876fe
Protect against nullish 'timeout' and other insane values
simonsinclair Jan 8, 2020
78b962a
Refactor TIMEOUT_MS to be configurable
simonsinclair Jan 8, 2020
e163eb1
Update 'shoulder render an iframe' test
simonsinclair Jan 8, 2020
b8395ad
Add 'should render a timeout message...' test
simonsinclair Jan 8, 2020
ec760c9
Merge branch 'latest' of https://github.com/bbc/psammead into media-p…
simonsinclair Jan 8, 2020
ae78387
Add 'timeoutMs' prop to README
simonsinclair Jan 8, 2020
6fb2afe
Editor whitespace cleanup
simonsinclair Jan 8, 2020
06c0ada
Merge branch 'latest' into media-player-timeout
simonsinclair Jan 9, 2020
e8072b0
Update timeout message
simonsinclair Jan 9, 2020
a3d06fe
Merge branch 'latest' into media-player-timeout
simonsinclair Jan 9, 2020
9132b3a
Merge branch 'latest' into media-player-timeout
simonsinclair Jan 9, 2020
299ffb7
Amend default timeoutMs value on parent for clarity
simonsinclair Jan 9, 2020
1ee9c6e
Merge branch 'latest' of https://github.com/bbc/psammead into media-p…
simonsinclair Jan 9, 2020
d72cab9
Merge branch 'media-player-timeout' of https://github.com/bbc/psammea…
simonsinclair Jan 9, 2020
6e005c7
Let me fix that
simonsinclair Jan 9, 2020
9601a5d
Merge branch 'latest' of https://github.com/bbc/psammead into media-p…
simonsinclair Jan 9, 2020
fb761f2
Merge branch 'latest' into media-player-timeout
simonsinclair Jan 10, 2020
d51abc5
Merge branch 'latest' of https://github.com/bbc/psammead into media-p…
simonsinclair Jan 10, 2020
d804acd
Merge branch 'media-player-timeout' of https://github.com/bbc/psammea…
simonsinclair Jan 10, 2020
48619b0
Improve useTimeout comments
simonsinclair Jan 10, 2020
a02664e
Validate iframeRef before binding events
simonsinclair Jan 10, 2020
e3d2d4f
Merge branch 'latest' into media-player-timeout
simonsinclair Jan 13, 2020
6cf8d3e
Merge branch 'latest' into media-player-timeout
paruchurisilpa Jan 13, 2020
24d9783
Merge branch 'latest' into media-player-timeout
simonsinclair Jan 13, 2020
08390da
Merge branch 'latest' of https://github.com/bbc/psammead into media-p…
simonsinclair Jan 14, 2020
564b400
Use afterEach cleanup in favour of unmount
simonsinclair Jan 14, 2020
6214146
Add 'should not render a timeout message...' test
simonsinclair Jan 14, 2020
b27eaac
Update test title for consistency
simonsinclair Jan 14, 2020
336c70b
Add useTimeout.test.jsx
simonsinclair Jan 14, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/components/psammead-media-player/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<!-- prettier-ignore -->
| Version | Description |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| 2.5.0 | [PR#2836](https://github.com/bbc/psammead/pull/2836) Add AV embed timeout handling. |
| 2.4.4 | [PR#2830](https://github.com/bbc/psammead/pull/2830) Talos - Bump Dependencies - @bbc/psammead-play-button |
| 2.4.3 | [PR#2827](https://github.com/bbc/psammead/pull/2827) Talos - Bump Dependencies - @bbc/psammead-assets |
| 2.4.2 | [PR#2806](https://github.com/bbc/psammead/pull/2806) Fix StyledPlaceholder cursor when Javascript is disabled |
Expand Down
25 changes: 14 additions & 11 deletions packages/components/psammead-media-player/README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# psammead-media-player &middot; [![Known Vulnerabilities](https://snyk.io/test/github/bbc/psammead/badge.svg?targetFile=packages%2Fcomponents%2Fpsammead-brand%2Fpackage.json)](https://snyk.io/test/github/bbc/psammead?targetFile=packages%2Fcomponents%2Fpsammead-brand%2Fpackage.json) [![Dependency Status](https://david-dm.org/bbc/psammead.svg?path=packages/components/psammead-media-player)](https://david-dm.org/bbc/psammead?path=packages/components/psammead-media-player) [![peerDependencies Status](https://david-dm.org/bbc/psammead/peer-status.svg?path=packages/components/psammead-media-player)](https://david-dm.org/bbc/psammead?path=packages/components/psammead-media-player&type=peer) [![Storybook](https://raw.githubusercontent.com/storybooks/brand/master/badge/badge-storybook.svg?sanitize=true)](https://bbc.github.io/psammead/?path=/story/brand--default) [![GitHub license](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/bbc/psammead/blob/latest/LICENSE) [![npm version](https://img.shields.io/npm/v/@bbc/psammead-media-player.svg)](https://www.npmjs.com/package/@bbc/psammead-media-player) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/bbc/psammead/blob/latest/CONTRIBUTING.md)
# psammead-media-player &middot; [![Known Vulnerabilities](https://snyk.io/test/github/bbc/psammead/badge.svg?targetFile=packages%2Fcomponents%2Fpsammead-brand%2Fpackage.json)](https://snyk.io/test/github/bbc/psammead?targetFile=packages%2Fcomponents%2Fpsammead-brand%2Fpackage.json) [![Dependency Status](https://david-dm.org/bbc/psammead.svg?path=packages/components/psammead-media-player)](https://david-dm.org/bbc/psammead?path=packages/components/psammead-media-player) [![peerDependencies Status](https://david-dm.org/bbc/psammead/peer-status.svg?path=packages/components/psammead-media-player)](https://david-dm.org/bbc/psammead?path=packages/components/psammead-media-player&type=peer) [![Storybook](https://raw.githubusercontent.com/storybooks/brand/master/badge/badge-storybook.svg?sanitize=true)](https://bbc.github.io/psammead/?path=/story/brand--default) [![GitHub license](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/bbc/psammead/blob/latest/LICENSE) [![npm version](https://img.shields.io/npm/v/@bbc/psammead-media-player.svg)](https://www.npmjs.com/package/@bbc/psammead-media-player) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/bbc/psammead/blob/latest/CONTRIBUTING.md)

## Description
The `psammead-media-player` component exports two versions of our media player: an AMP version, and an Canonical version.

At it's core, this component returns an `iframe` that is designed to frame a media asset.
At it's core, this component returns an `iframe` that is designed to frame a media asset.
simonsinclair marked this conversation as resolved.
Show resolved Hide resolved
The AMP variant will render an `amp-iframe` with a nested `amp-img` to use as a placeholder.
The Canonical variant will render a placeholder, that when clicked will load the `iframe` into view.

Expand All @@ -29,7 +29,7 @@ This component to be used at any point on the page, specifically when a media pl
| [`mediaInfo`](#mediaInfo) | object | Yes | - | `{ title: 'A vertical video pretending to be a cat title', duration: '2:30', durationSpoken: '2 minutes 11 seconds', datetime: 'PT2M30S' }`|
| `noJsClassName` | string | No | `null` | `'no-js'` |
| `noJsMessage` | string | Yes | - | `'This media cannot play in your browser. Please enable Javascript or use a different browser.'` |

| `timeoutMs` | number | No | `5000` | `9001` |

The `src` prop is required, as it tells the component what page it needs to embed.
The `placeholderSrcset` prop is not required, as it allows image responsiveness and optimization depending on the size of the screen.
Expand All @@ -38,8 +38,9 @@ The `portrait` prop is not required, and defaults to `false`. This is to support
The `showPlaceholder` boolean prop is also not required, and defaults to `true`.
Assuming `showPlaceholder` is `true`, the `placeholderSrc` will be what image to display as the placeholder.
The `noJsClassName` is an optional prop that defaults to `null` and is used to add styling support to certain elements when javascript is disabled in the browser.
The `noJsMessage` is a required prop used display a fallback text when javascript is disabled in the browser.
The `noJsMessage` is a required prop used display a fallback text when javascript is disabled in the browser.
The `mediaInfo` prop is required, and has the following properties.
The `timeoutMs` prop allows you to override the default timeout value. If media fails to load in this time, users are shown a timeout message.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be good to document that this only applies to canonical and not AMP?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not immediately obvious from the files changed diff on GitHub, but the readme is split into Canonical and AMP sections and this prop is only listed beneath Canonical.


#### mediaInfo

Expand Down Expand Up @@ -69,18 +70,19 @@ The `placeholderSrc` prop is required for AMP, as in order to have the component
```js
import { CanonicalMediaPlayer } from '@bbc/psammead-media-player';

const Container = ({
src,
const Container = ({
src,
skin,
title,
title,
service,
portrait,
showPlaceholder,
placeholderSrc,
placeholderSrcset,
portrait,
showPlaceholder,
placeholderSrc,
placeholderSrcset,
mediaInfo,
noJsClassName,
noJsMessage,
timeoutMs,
}) => (
<CanonicalMediaPlayer
src={src}
Expand All @@ -94,6 +96,7 @@ const Container = ({
mediaInfo={mediaInfo}
noJsClassName={noJsClassName}
noJsMessage={noJsMessage}
timeoutMs={timeoutMs}
/>
)
```
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/components/psammead-media-player/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@bbc/psammead-media-player",
"version": "2.4.4",
"version": "2.5.0",
"description": "Provides a media player with optional placeholder",
"main": "dist/index.js",
"module": "esm/index.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,30 @@

exports[`Media Player: Canonical should render an iframe 1`] = `
.c0 {
left: 0;
overflow: hidden;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}

.c1 {
border: 0;
overflow: hidden;
height: 100%;
width: 100%;
}

<iframe
allow="autoplay; fullscreen"
allowfullscreen=""
<div
class="c0"
scrolling="no"
src="https://foo.bar/iframe"
title="Media player"
/>
>
<iframe
allow="autoplay; fullscreen"
allowfullscreen=""
class="c1"
scrolling="no"
src="https://foo.bar/iframe"
title="Media player"
/>
</div>
`;
69 changes: 47 additions & 22 deletions packages/components/psammead-media-player/src/Canonical/index.jsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,70 @@
import React from 'react';
import { string } from 'prop-types';
import styled from 'styled-components';

const Canonical = ({ src, title, placeholderSrc }) => {
const backgroundStyle = `
background-image: url(${placeholderSrc});
background-repeat: no-repeat;
background-size: contain;
`;
import React, { useState, useRef } from 'react';
import { string, number } from 'prop-types';
import styled, { css } from 'styled-components';
import Guidance from '../Guidance';
import useTimeout from './useTimeout';

const StyledIframe = styled.iframe`
const Canonical = ({ service, src, title, placeholderSrc, timeoutMs }) => {
const [hasTimedOut, setHasTimedOut] = useState(null);
chris-hinds marked this conversation as resolved.
Show resolved Hide resolved
const iframeRef = useRef(null);

useTimeout(setHasTimedOut, iframeRef, timeoutMs);

const CanonicalWrapper = styled.div`
left: 0;
overflow: hidden;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
${placeholderSrc &&
css`
background-image: url(${placeholderSrc});
background-repeat: no-repeat;
background-size: contain;
`}
`;

const StyledIframe = styled.iframe`
border: 0;
overflow: hidden;
${placeholderSrc ? backgroundStyle : ''}
height: 100%;
width: 100%;
`;

return (
<StyledIframe
src={src}
title={title}
allow="autoplay; fullscreen"
scrolling="no"
gesture="media"
allowFullScreen
/>
<CanonicalWrapper>
{hasTimedOut ? (
<Guidance
service={service}
guidanceMessage="There was a problem loading this content. Please check your internet connection and try again."
noJsMessage=""
/>
) : (
<StyledIframe
ref={iframeRef}
src={src}
title={title}
allow="autoplay; fullscreen"
scrolling="no"
gesture="media"
allowFullScreen
/>
)}
</CanonicalWrapper>
);
};

Canonical.propTypes = {
service: string.isRequired,
src: string.isRequired,
title: string.isRequired,
placeholderSrc: string,
timeoutMs: number,
};

Canonical.defaultProps = {
placeholderSrc: null,
timeoutMs: 5000,
};

export default Canonical;
Original file line number Diff line number Diff line change
@@ -1,10 +1,38 @@
import React from 'react';
import { render, act } from '@testing-library/react';
import { shouldMatchSnapshot } from '@bbc/psammead-test-helpers';
import Canonical from '.';

describe('Media Player: Canonical', () => {
shouldMatchSnapshot(
'should render an iframe',
<Canonical src="https://foo.bar/iframe" title="Media player" />,
<Canonical
src="https://foo.bar/iframe"
title="Media player"
service="news"
/>,
);

it('should render a timeout message if timeout timer expires', () => {
jest.useFakeTimers();
const { getByText, unmount } = render(
<Canonical
src="https://foo.bar/iframe"
title="Media player"
service="news"
timeoutMs={2000}
/>,
);

act(() => {
jest.runTimersToTime(3000);
});

expect(
getByText(
'There was a problem loading this content. Please check your internet connection and try again.',
),
);
unmount();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useRef, useEffect } from 'react';

/**
* useTimeout Hook.
* @param {function} callback A callback function that's passed `true` if the timer
* completes, and `false` if it's interrupted.
* @param {object} iframeRef A React Ref. providing access to an iframe DOM node.
* @param {number} timeout The number of milliseconds until the timer completes.
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments in this file are excellent.

const useTimeout = (callback, iframeRef, timeout) => {
// Persist a reference to the timer across renders. This is used to clearTimeout.
const timer = useRef(null);

const handleLoad = () => {
clearTimeout(timer.current);
callback(false);
};

// The function we pass to useEffect will run on mount, and the one we return will
// run on unmount. We don't do anything if the value of `timeout` can't be used.
// eslint-disable-next-line consistent-return
useEffect(() => {
if (timeout > 0) {
iframeRef.current.addEventListener('load', handleLoad, { once: true });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The value of .current is guaranteed to be defined at this point, right?

Copy link
Contributor Author

@simonsinclair simonsinclair Jan 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If iframeRef is created as documented (as a React Ref), it will be, but perhaps this should be more defensively written. I deliberated over this for a while and appreciate you raising it. It should probably throw if current is falsy. I'll make this change now.

timer.current = setTimeout(() => {
callback(true);
}, timeout);
return () => clearTimeout(timer.current);
}
}, []);
};

export default useTimeout;
Original file line number Diff line number Diff line change
Expand Up @@ -685,26 +685,35 @@ exports[`Media Player: Canonical Entry renders an iframe when showPlaceholder is
}

.c1 {
left: 0;
overflow: hidden;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}

.c2 {
border: 0;
overflow: hidden;
height: 100%;
width: 100%;
}

<div
class="c0"
>
<iframe
allow="autoplay; fullscreen"
allowfullscreen=""
<div
class="c1"
scrolling="no"
src="http://foo.bar/iframe"
title="Media player"
/>
>
<iframe
allow="autoplay; fullscreen"
allowfullscreen=""
class="c2"
scrolling="no"
src="http://foo.bar/iframe"
title="Media player"
/>
</div>
</div>
`;

Expand All @@ -716,26 +725,35 @@ exports[`Media Player: Canonical Entry renders the audio skin 1`] = `
}

.c1 {
left: 0;
overflow: hidden;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}

.c2 {
border: 0;
overflow: hidden;
height: 100%;
width: 100%;
}

<div
class="c0"
>
<iframe
allow="autoplay; fullscreen"
allowfullscreen=""
<div
class="c1"
scrolling="no"
src="https://www.test.bbc.com/ws/av-embeds/media/bbc_korean_radio/liveradio"
title="Audio player"
/>
>
<iframe
allow="autoplay; fullscreen"
allowfullscreen=""
class="c2"
scrolling="no"
src="https://www.test.bbc.com/ws/av-embeds/media/bbc_korean_radio/liveradio"
title="Audio player"
/>
</div>
</div>
`;

Expand Down
Loading