/
video.tsx
139 lines (123 loc) · 4.26 KB
/
video.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import { Processor, Element } from "@frontity/html2react/types";
import { Packages } from "../../types";
import { Head } from "frontity";
import { httpToHttps } from "../utils";
/**
* Props for the {@link AMPVideo} component.
*/
interface VideoProps {
/**
* The className that might be passed to the component after being
* having been generated by the emotion babel plugin.
*/
className: string;
/**
* Corresponds to the `autoplay` property of the `<video>` element.
*/
autoPlay: string;
/**
* Corresponds to the `loop` property of the `<video>` element.
*/
loop: string;
/**
* Corresponds to the `controls` property of the `<video>` element.
*/
controls: string;
/**
* Corresponds to the `muted` property of the `<video>` element.
*/
muted: string;
}
/**
* The component that renders an amp-video component in place of a regular
* video and adds the required AMP script for amp-video in the head.
*
* @param props - The props to pass the the amp-video.
*
* @returns A react component.
*/
const AMPVideo: React.FC<VideoProps> = ({
className,
autoPlay,
loop,
controls,
muted,
...props
}) => {
return (
<>
<Head>
<script
// We have to explicitly pass undefined, otherwise the attribute is
// passed to the DOM like async="true" and AMP does not allow that.
async={undefined}
custom-element="amp-video"
src="https://cdn.ampproject.org/v0/amp-video-0.1.js"
/>
</Head>
<amp-video
class={className}
// If the attribute's value is truthy, we set it to "" so that
// the attribute appears as a boolean without a value, like: <video
// loop>
// This is required per AMP validation rules
// https://stackoverflow.com/a/50996065/2638310
{...props}
{...(autoPlay ? { autoPlay: "" } : undefined)}
{...(controls ? { controls: "" } : undefined)}
{...(loop ? { loop: "" } : undefined)}
{...(muted ? { muted: "" } : undefined)}
/>
</>
);
};
export const video: Processor<Element, Packages> = {
name: "amp-video",
test: ({ node }) => node.component === "video",
processor: ({ node }) => {
node.component = AMPVideo;
// AMP requires that the file is loaded over HTTPS
node = httpToHttps(node);
// Create an array that will hold all the child elements of this
// video element. We start by adding all child `source` and `track`
// elements (if they exist)
let children = node.children.filter(
(child: Element) =>
child.component === "source" || child.component === "track"
);
// Change http:// to https:// in the child `source` elements
children = children.map(httpToHttps);
// Find the first child that has a `placeholder` prop and if it exists add
// it to the array of children. We only add the first one because there
// can only be one placeholder element.
const placeholder = node.children.find((child: Element) =>
Object.keys(child.props).includes("placeholder")
);
placeholder && children.push(placeholder);
// Find the first child that has a `fallback` prop and if it exists add
// it to the array of children. We only add the first one because there
// can only be one fallback element.
const fallback = node.children.find((child: Element) =>
Object.keys(child.props).includes("fallback")
);
// And if it exists, add it to the array of children
fallback && children.push(fallback);
node.children = children;
// For now if the video does not specify width & height we default to
// a 9:16 aspect ratio.
const width = node.props.width || node.props["data-origwidth"];
const height = node.props.width || node.props["data-origheight"];
if (parseInt(width, 10) > 0 && parseInt(height, 10) > 0) {
node.props.width = width;
node.props.height = height;
} else {
// When used together with layout="responsive", it does not mean that the
// video will have a specific height & width but rather that it will scale
// responsively preserving the aspect ratio between the height and width.
node.props.height = 9;
node.props.width = 16;
}
node.props["layout"] = "responsive";
return node;
},
};