Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 6 additions & 5 deletions packages/web-shared/components/ContentSingle.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,12 @@ function ContentSingle(props = {}) {
const invalidPage = !props.loading && !props.data;

// Video details
const videoMedia = props.data?.videos?.[0];
const playableMedia = props.data?.videos?.[0] || props.data?.audios?.[0];
const shouldLoadMediaProgress = playableMedia?.__typename === 'VideoMedia';

const { userProgress } = useVideoMediaProgress({
variables: { id: videoMedia?.id },
skip: !videoMedia?.id,
variables: { id: playableMedia?.id },
skip: !shouldLoadMediaProgress || !playableMedia?.id,
});

useEffect(() => {
Expand Down Expand Up @@ -273,9 +274,9 @@ function ContentSingle(props = {}) {
title: props.data?.title,
}}
/>
{coverImage?.sources[0]?.uri || videoMedia ? (
{coverImage?.sources[0]?.uri || playableMedia ? (
<Box mb="base" borderRadius="xl" overflow="hidden" width="100%">
{videoMedia ? (
{playableMedia ? (
<VideoPlayer
userProgress={userProgress}
parentNode={props.data}
Expand Down
39 changes: 22 additions & 17 deletions packages/web-shared/components/VideoPlayer/VideoPlayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,21 @@ function VideoPlayer(props = {}) {
const [progressTime, setProgressTime] = useState(0);
const [paused, setPaused] = useState(false);

const hasSource = (video) => video.sources?.some((source) => source?.uri);
const mediaItems = props.parentNode?.videos?.length
? props.parentNode.videos
: props.parentNode?.audios || [];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think I'm okay with this but just want to call out that this is a variance from how we do this on mobile.

On mobile, the video player only plays the video if one exists. There is now a separate audio player that exists below the cover image that plays audio if there is audio.

If there is both audio and video available, both players are visible.

I don't think we have an audio player on web right now?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@conrad-vanl is the legacy embed repo. I added audio support in a previous PR

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

seacoast-audio

const hasSource = (media) => media.sources?.some((source) => source?.uri);

// will find the first HLS video playlist provided
const hlsMedia = props.parentNode?.videos?.find((video) =>
video.sources?.some((source) => source?.uri?.includes('.m3u8'))
const hlsMedia = mediaItems.find((media) =>
media.sources?.some((source) => source?.uri?.includes('.m3u8'))
);
const youtubeMedia = props.parentNode?.videos?.find((video) =>
video.sources?.some((source) => source?.uri?.includes('youtu'))
const youtubeMedia = mediaItems.find((media) =>
media.sources?.some((source) => source?.uri?.includes('youtu'))
);
const fallbackMedia = props.parentNode?.videos?.find(hasSource);
const videoMedia = youtubeMedia || hlsMedia || fallbackMedia;
const fallbackMedia = mediaItems.find(hasSource);
const playableMedia = youtubeMedia || hlsMedia || fallbackMedia;
const shouldTrackProgress = playableMedia?.__typename === 'VideoMedia';

const userProgress = props.userProgress || { playhead: 0, complete: false };

Expand All @@ -59,10 +63,10 @@ function VideoPlayer(props = {}) {
}
previouslyReportedPlayhead.current = data?.progress;

if (videoMedia.id) {
if (shouldTrackProgress && playableMedia?.id) {
_interactWithNode({
variables: {
nodeId: videoMedia.id,
nodeId: playableMedia.id,
action,
data,
},
Expand All @@ -75,7 +79,7 @@ function VideoPlayer(props = {}) {

cache.modify({
id: cache.identify({
__typename: 'VideoMedia',
__typename: playableMedia.__typename,
id: variables.nodeId,
}),
fields: {
Expand All @@ -91,7 +95,7 @@ function VideoPlayer(props = {}) {
});
}
},
[_interactWithNode, videoMedia, isLiveStreaming]
[_interactWithNode, playableMedia, isLiveStreaming, shouldTrackProgress]
);

// This *could* be extracted to a local hook.
Expand All @@ -111,15 +115,15 @@ function VideoPlayer(props = {}) {
sessionId: sessionId.current,
contentAssetId: get(props, 'parentNode.id'),
parentOriginId: get(props, 'parentNode.originId'),
originId: get(props, 'parentNode.videos[0].originId'),
originSource: get(props, 'parentNode.videos[0].originType'),
originId: get(playableMedia, 'originId'),
originSource: get(playableMedia, 'originType'),
videoPlayer: 'Apollos Web',
sound: 1,
fullScreen: true,
livestream: false,
timestamp: today.toUTCString(),
title: get(props, 'parentNode.title'),
totalLength: get(props, 'parentNode.videos[0].duration'),
totalLength: get(playableMedia, 'duration'),
publishedAt: new Date(parseInt(get(props, 'parentNode.publishDate', '0'), 10)),
...livestreamProperties,
..._analyticsData,
Expand Down Expand Up @@ -247,14 +251,14 @@ function VideoPlayer(props = {}) {

const source = isLiveStreaming
? props.parentNode?.stream?.sources?.find((streamSource) => streamSource?.uri)?.uri
: videoMedia?.sources?.find((videoSource) => videoSource?.uri)?.uri;
: playableMedia?.sources?.find((mediaSource) => mediaSource?.uri)?.uri;

if (props.parentNode?.videos?.embedHtml) {
if (playableMedia?.embedHtml) {
return (
<EmbededPlayer
{...props}
dangerouslySetInnerHTML={{
__html: parseHTMLContent(props.parentNode?.videos?.embedHtml, {
__html: parseHTMLContent(playableMedia.embedHtml, {
sanitizeOptions: {
ALLOWED_TAGS: ['iframe'],
ADD_ATTR: ['allow', 'allowfullscreen', 'frameborder', 'scrolling'],
Expand Down Expand Up @@ -321,6 +325,7 @@ VideoPlayer.propTypes = {
summary: PropTypes.string,
title: PropTypes.string,
videos: PropTypes.arrayOf(VideoMedia),
audios: PropTypes.arrayOf(VideoMedia),
}),
onVideoEnd: PropTypes.func,
livestream: PropTypes.shape({
Expand Down
13 changes: 13 additions & 0 deletions packages/web-shared/fragments/fragments.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ const VIDEO_MEDIA_FIELDS = gql`
}
`;

const AUDIO_MEDIA_FIELDS = gql`
fragment AudioMediaFields on AudioMedia {
__typename
id
key
name
sources {
uri
}
}
`;

const CONTENT_CARD_FRAGMENT = gql`
fragment ContentCard on CardListItem {
id
Expand Down Expand Up @@ -112,6 +124,7 @@ const EVENT_FRAGMENT = gql`

export {
VIDEO_MEDIA_FIELDS,
AUDIO_MEDIA_FIELDS,
CONTENT_NODE_FRAGMENT,
CONTENT_SINGLE_FRAGMENT,
EVENT_FRAGMENT,
Expand Down
8 changes: 8 additions & 0 deletions packages/web-shared/hooks/useContentItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { gql, useQuery } from '@apollo/client';

import {
VIDEO_MEDIA_FIELDS,
AUDIO_MEDIA_FIELDS,
CONTENT_NODE_FRAGMENT,
CONTENT_SINGLE_FRAGMENT,
EVENT_FRAGMENT,
} from '../fragments';

export const GET_CONTENT_ITEM = gql`
${VIDEO_MEDIA_FIELDS}
${AUDIO_MEDIA_FIELDS}
${CONTENT_NODE_FRAGMENT}
${CONTENT_SINGLE_FRAGMENT}
${EVENT_FRAGMENT}
Expand All @@ -28,6 +30,9 @@ export const GET_CONTENT_ITEM = gql`
videos {
...VideoMediaFields
}
audios {
...AudioMediaFields
}
}

query getContentItem($id: ID!) {
Expand Down Expand Up @@ -77,6 +82,9 @@ export const GET_CONTENT_ITEM = gql`
videos {
...VideoMediaFields
}
audios {
...AudioMediaFields
}
parentItem {
...SubCardFragment
}
Expand Down
26 changes: 26 additions & 0 deletions web-embeds/src/VideoPlayer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,30 @@ describe("VideoPlayer", () => {
"https://example.com/audio.mp3"
);
});

it("renders an AudioMedia source when no videos are present", () => {
render(
<VideoPlayer
coverImage="https://example.com/cover.jpg"
parentNode={{
id: "content-2",
publishDate: "0",
title: "Audio only content",
audios: [
{
__typename: "AudioMedia",
id: "audio-1",
sources: [{ uri: "https://example.com/audio-only.mp3" }],
},
],
}}
userProgress={{ complete: false, playhead: 0 }}
/>
);

expect(screen.getByTestId("player-shell")).toHaveAttribute(
"data-url",
"https://example.com/audio-only.mp3"
);
});
});
Loading