sanity-plugin-mux-input 3.0.5 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +28 -28
- package/dist/index.js.map +1 -1
- package/package.json +5 -15
- package/dist/index.cjs +0 -5746
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -288
- package/dist/index.d.cts.map +0 -1
- package/sanity.json +0 -8
- package/src/_exports/index.ts +0 -73
- package/src/actions/assets.ts +0 -152
- package/src/actions/secrets.ts +0 -110
- package/src/actions/upload.ts +0 -308
- package/src/clients/upChunkObservable.ts +0 -54
- package/src/components/AddCaptionDialog.tsx +0 -440
- package/src/components/CaptionsDialog.tsx +0 -23
- package/src/components/ConfigureApi.styled.tsx +0 -19
- package/src/components/ConfigureApi.tsx +0 -296
- package/src/components/DraggableWatermark.tsx +0 -885
- package/src/components/EditCaptionDialog.tsx +0 -511
- package/src/components/EditThumbnailDialog.tsx +0 -121
- package/src/components/ErrorBoundaryCard.tsx +0 -97
- package/src/components/FileInputButton.tsx +0 -54
- package/src/components/FileInputMenuItem.styled.tsx +0 -36
- package/src/components/FileInputMenuItem.tsx +0 -85
- package/src/components/FormField.tsx +0 -38
- package/src/components/IconInfo.tsx +0 -22
- package/src/components/ImportVideosFromMux.tsx +0 -339
- package/src/components/Input.styled.tsx +0 -22
- package/src/components/Input.tsx +0 -78
- package/src/components/InputBrowser.tsx +0 -41
- package/src/components/MuxLogo.tsx +0 -42
- package/src/components/Onboard.tsx +0 -65
- package/src/components/PageSelector.tsx +0 -54
- package/src/components/Player.styled.tsx +0 -11
- package/src/components/Player.tsx +0 -117
- package/src/components/PlayerActionsMenu.tsx +0 -191
- package/src/components/ResyncMetadata.tsx +0 -278
- package/src/components/SelectAsset.tsx +0 -39
- package/src/components/SelectSortOptions.tsx +0 -45
- package/src/components/SpinnerBox.tsx +0 -16
- package/src/components/StudioTool.tsx +0 -24
- package/src/components/TextTracksEditor.tsx +0 -117
- package/src/components/TextTracksManager.tsx +0 -738
- package/src/components/UploadConfiguration.tsx +0 -696
- package/src/components/UploadPlaceholder.tsx +0 -88
- package/src/components/UploadProgress.tsx +0 -80
- package/src/components/Uploader.styled.tsx +0 -65
- package/src/components/Uploader.tsx +0 -499
- package/src/components/VideoDetails/DeleteDialog.tsx +0 -148
- package/src/components/VideoDetails/VideoDetails.tsx +0 -358
- package/src/components/VideoDetails/VideoReferences.tsx +0 -63
- package/src/components/VideoDetails/useVideoDetails.ts +0 -103
- package/src/components/VideoInBrowser.tsx +0 -245
- package/src/components/VideoMetadata.tsx +0 -45
- package/src/components/VideoPlayer.tsx +0 -241
- package/src/components/VideoThumbnail.tsx +0 -139
- package/src/components/VideosBrowser.tsx +0 -100
- package/src/components/documentPreview/DocumentPreview.tsx +0 -84
- package/src/components/documentPreview/DraftStatus.tsx +0 -34
- package/src/components/documentPreview/MissingSchemaType.tsx +0 -32
- package/src/components/documentPreview/PaneItemPreview.tsx +0 -67
- package/src/components/documentPreview/PublishedStatus.tsx +0 -35
- package/src/components/documentPreview/TimeAgo.tsx +0 -12
- package/src/components/icons/Audio.tsx +0 -13
- package/src/components/icons/Resolution.tsx +0 -12
- package/src/components/icons/StopWatch.tsx +0 -20
- package/src/components/icons/ToolIcon.tsx +0 -19
- package/src/components/uploadConfiguration/PlaybackPolicy.tsx +0 -133
- package/src/components/uploadConfiguration/PlaybackPolicyOption.tsx +0 -76
- package/src/components/uploadConfiguration/PlaybackPolicyWarning.tsx +0 -29
- package/src/components/uploadConfiguration/ResolutionTierSelector.tsx +0 -72
- package/src/components/uploadConfiguration/StaticRenditionSelector.tsx +0 -180
- package/src/components/withFocusRing/helpers.ts +0 -24
- package/src/components/withFocusRing/index.ts +0 -1
- package/src/components/withFocusRing/withFocusRing.ts +0 -30
- package/src/context/DialogStateContext.tsx +0 -33
- package/src/context/DrmPlaybackWarningContext.tsx +0 -97
- package/src/hooks/useAccessControl.ts +0 -13
- package/src/hooks/useAssetDocumentValues.ts +0 -11
- package/src/hooks/useAssets.ts +0 -73
- package/src/hooks/useCancelUpload.ts +0 -22
- package/src/hooks/useClient.ts +0 -8
- package/src/hooks/useDialogState.ts +0 -11
- package/src/hooks/useDocReferences.ts +0 -21
- package/src/hooks/useFetchFileSize.ts +0 -55
- package/src/hooks/useImportMuxAssets.ts +0 -132
- package/src/hooks/useInView.ts +0 -41
- package/src/hooks/useMediaMetadata.ts +0 -104
- package/src/hooks/useMuxAssets.ts +0 -179
- package/src/hooks/useMuxPolling.ts +0 -52
- package/src/hooks/useResyncAsset.ts +0 -110
- package/src/hooks/useResyncMuxMetadata.ts +0 -169
- package/src/hooks/useSaveSecrets.ts +0 -78
- package/src/hooks/useSecretsDocumentValues.ts +0 -38
- package/src/hooks/useSecretsFormState.ts +0 -47
- package/src/plugin.tsx +0 -31
- package/src/sanity-ui.d.ts +0 -5
- package/src/schema.ts +0 -196
- package/src/util/addKeysToMuxData.ts +0 -30
- package/src/util/asserters.ts +0 -23
- package/src/util/assetTitlePlaceholder.ts +0 -31
- package/src/util/constants.ts +0 -15
- package/src/util/convertWatermarkToMux.ts +0 -160
- package/src/util/createSearchFilter.ts +0 -76
- package/src/util/createUrlParamsObject.ts +0 -29
- package/src/util/extractFiles.ts +0 -67
- package/src/util/formatBytes.ts +0 -31
- package/src/util/formatDriveShareLink.ts +0 -64
- package/src/util/formatSeconds.ts +0 -48
- package/src/util/generateJwt.ts +0 -57
- package/src/util/getAnimatedPosterSrc.ts +0 -26
- package/src/util/getPlaybackPolicy.ts +0 -69
- package/src/util/getPosterSrc.ts +0 -28
- package/src/util/getVideoMetadata.ts +0 -23
- package/src/util/getVideoSrc.ts +0 -23
- package/src/util/parsers.ts +0 -5
- package/src/util/pluginVersion.ts +0 -5
- package/src/util/readSecrets.ts +0 -38
- package/src/util/roundPxString.ts +0 -16
- package/src/util/textTracks.ts +0 -222
- package/src/util/tryWithSuspend.ts +0 -22
- package/src/util/types.ts +0 -566
- package/v2-incompatible.js +0 -11
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
import {CheckmarkIcon, EditIcon, LockIcon, PlayIcon} from '@sanity/icons'
|
|
2
|
-
import {Button, Card, Stack, Text, Tooltip} from '@sanity/ui'
|
|
3
|
-
import React, {useState} from 'react'
|
|
4
|
-
import {styled} from 'styled-components'
|
|
5
|
-
|
|
6
|
-
import {DRMWarningDialog, useDrmPlaybackWarningContext} from '../context/DrmPlaybackWarningContext'
|
|
7
|
-
import {THUMBNAIL_ASPECT_RATIO} from '../util/constants'
|
|
8
|
-
import {getPlaybackPolicy} from '../util/getPlaybackPolicy'
|
|
9
|
-
import {type VideoAssetDocument} from '../util/types'
|
|
10
|
-
import IconInfo from './IconInfo'
|
|
11
|
-
import {AudioIcon} from './icons/Audio'
|
|
12
|
-
import VideoMetadata from './VideoMetadata'
|
|
13
|
-
import VideoPlayer, {assetIsAudio} from './VideoPlayer'
|
|
14
|
-
import VideoThumbnail from './VideoThumbnail'
|
|
15
|
-
|
|
16
|
-
const PlayButton = styled.button`
|
|
17
|
-
display: block;
|
|
18
|
-
padding: 0;
|
|
19
|
-
margin: 0;
|
|
20
|
-
border: none;
|
|
21
|
-
border-radius: 0.1875rem;
|
|
22
|
-
position: relative;
|
|
23
|
-
cursor: pointer;
|
|
24
|
-
|
|
25
|
-
&::after {
|
|
26
|
-
content: '';
|
|
27
|
-
background: var(--card-fg-color);
|
|
28
|
-
opacity: 0;
|
|
29
|
-
display: block;
|
|
30
|
-
position: absolute;
|
|
31
|
-
inset: 0;
|
|
32
|
-
z-index: 10;
|
|
33
|
-
transition: 0.15s ease-out;
|
|
34
|
-
border-radius: inherit;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
> div[data-play] {
|
|
38
|
-
z-index: 11;
|
|
39
|
-
opacity: 0;
|
|
40
|
-
transition: 0.15s 0.05s ease-out;
|
|
41
|
-
position: absolute;
|
|
42
|
-
left: 50%;
|
|
43
|
-
top: 50%;
|
|
44
|
-
transform: translate(-50%, -50%);
|
|
45
|
-
color: var(--card-fg-color);
|
|
46
|
-
background: var(--card-bg-color);
|
|
47
|
-
width: auto;
|
|
48
|
-
height: 30%;
|
|
49
|
-
aspect-ratio: 1;
|
|
50
|
-
border-radius: 100%;
|
|
51
|
-
display: flex;
|
|
52
|
-
justify-content: center;
|
|
53
|
-
align-items: center;
|
|
54
|
-
box-sizing: border-box;
|
|
55
|
-
> svg {
|
|
56
|
-
display: block;
|
|
57
|
-
width: 70%;
|
|
58
|
-
height: auto;
|
|
59
|
-
// Visual balance to center-align the icon
|
|
60
|
-
transform: translateX(5%);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
&:hover,
|
|
65
|
-
&:focus {
|
|
66
|
-
&::after {
|
|
67
|
-
opacity: 0.3;
|
|
68
|
-
}
|
|
69
|
-
> div[data-play] {
|
|
70
|
-
opacity: 1;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
`
|
|
74
|
-
|
|
75
|
-
type RenderState = 'render-video' | 'pre-render-warn' | false
|
|
76
|
-
|
|
77
|
-
export default function VideoInBrowser({
|
|
78
|
-
onSelect,
|
|
79
|
-
onEdit,
|
|
80
|
-
asset,
|
|
81
|
-
}: {
|
|
82
|
-
onSelect?: (asset: VideoAssetDocument) => void
|
|
83
|
-
onEdit?: (asset: VideoAssetDocument) => void
|
|
84
|
-
asset: VideoAssetDocument
|
|
85
|
-
}) {
|
|
86
|
-
const [renderVideo, setRenderVideo] = useState<RenderState>(false)
|
|
87
|
-
const select = React.useCallback(() => onSelect?.(asset), [onSelect, asset])
|
|
88
|
-
const edit = React.useCallback(() => onEdit?.(asset), [onEdit, asset])
|
|
89
|
-
const {hasShownWarning} = useDrmPlaybackWarningContext()
|
|
90
|
-
|
|
91
|
-
if (!asset) {
|
|
92
|
-
return null
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const playbackPolicy = getPlaybackPolicy(asset)
|
|
96
|
-
const onClickPlay = () => {
|
|
97
|
-
if (playbackPolicy?.policy === 'drm' && !hasShownWarning) {
|
|
98
|
-
setRenderVideo('pre-render-warn')
|
|
99
|
-
} else {
|
|
100
|
-
setRenderVideo('render-video')
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return (
|
|
104
|
-
<Card
|
|
105
|
-
border
|
|
106
|
-
padding={2}
|
|
107
|
-
sizing="border"
|
|
108
|
-
radius={2}
|
|
109
|
-
style={{
|
|
110
|
-
position: 'relative',
|
|
111
|
-
}}
|
|
112
|
-
>
|
|
113
|
-
{playbackPolicy?.policy === 'signed' && (
|
|
114
|
-
<Tooltip
|
|
115
|
-
animate
|
|
116
|
-
content={
|
|
117
|
-
<Card padding={2} radius={2}>
|
|
118
|
-
<IconInfo icon={LockIcon} text="Signed playback policy" size={2} />
|
|
119
|
-
</Card>
|
|
120
|
-
}
|
|
121
|
-
placement="right"
|
|
122
|
-
fallbackPlacements={['top', 'bottom']}
|
|
123
|
-
portal
|
|
124
|
-
>
|
|
125
|
-
<Card
|
|
126
|
-
tone="caution"
|
|
127
|
-
style={{
|
|
128
|
-
borderRadius: '100%',
|
|
129
|
-
position: 'absolute',
|
|
130
|
-
left: '1em',
|
|
131
|
-
top: '1em',
|
|
132
|
-
zIndex: 11,
|
|
133
|
-
}}
|
|
134
|
-
padding={2}
|
|
135
|
-
border
|
|
136
|
-
>
|
|
137
|
-
<Text muted size={1}>
|
|
138
|
-
<LockIcon />
|
|
139
|
-
</Text>
|
|
140
|
-
</Card>
|
|
141
|
-
</Tooltip>
|
|
142
|
-
)}
|
|
143
|
-
{playbackPolicy?.policy === 'drm' && (
|
|
144
|
-
<Tooltip
|
|
145
|
-
animate
|
|
146
|
-
content={
|
|
147
|
-
<Card padding={2} radius={2}>
|
|
148
|
-
<IconInfo icon={LockIcon} text="DRM playback policy" size={2} />
|
|
149
|
-
</Card>
|
|
150
|
-
}
|
|
151
|
-
placement="right"
|
|
152
|
-
fallbackPlacements={['top', 'bottom']}
|
|
153
|
-
portal
|
|
154
|
-
>
|
|
155
|
-
<Card
|
|
156
|
-
tone="caution"
|
|
157
|
-
style={{
|
|
158
|
-
borderRadius: '0.25rem',
|
|
159
|
-
position: 'absolute',
|
|
160
|
-
left: '1em',
|
|
161
|
-
top: '1em',
|
|
162
|
-
zIndex: 11,
|
|
163
|
-
}}
|
|
164
|
-
padding={2}
|
|
165
|
-
border
|
|
166
|
-
>
|
|
167
|
-
<Text muted size={1} weight="semibold" style={{color: 'var(--card-icon-color)'}}>
|
|
168
|
-
DRM
|
|
169
|
-
</Text>
|
|
170
|
-
</Card>
|
|
171
|
-
</Tooltip>
|
|
172
|
-
)}
|
|
173
|
-
<Stack
|
|
174
|
-
space={3}
|
|
175
|
-
height="fill"
|
|
176
|
-
style={{
|
|
177
|
-
gridTemplateRows: 'min-content min-content 1fr',
|
|
178
|
-
}}
|
|
179
|
-
>
|
|
180
|
-
{renderVideo === 'pre-render-warn' && (
|
|
181
|
-
<DRMWarningDialog
|
|
182
|
-
onClose={() => {
|
|
183
|
-
setRenderVideo('render-video')
|
|
184
|
-
}}
|
|
185
|
-
/>
|
|
186
|
-
)}
|
|
187
|
-
{renderVideo === 'render-video' ? (
|
|
188
|
-
<VideoPlayer asset={asset} autoPlay forceAspectRatio={THUMBNAIL_ASPECT_RATIO} />
|
|
189
|
-
) : (
|
|
190
|
-
<PlayButton onClick={onClickPlay}>
|
|
191
|
-
<div data-play>
|
|
192
|
-
<PlayIcon />
|
|
193
|
-
</div>
|
|
194
|
-
{assetIsAudio(asset) ? (
|
|
195
|
-
<div
|
|
196
|
-
style={{
|
|
197
|
-
aspectRatio: THUMBNAIL_ASPECT_RATIO,
|
|
198
|
-
display: 'flex',
|
|
199
|
-
alignItems: 'center',
|
|
200
|
-
justifyContent: 'center',
|
|
201
|
-
}}
|
|
202
|
-
>
|
|
203
|
-
<AudioIcon width="3em" height="3em" />
|
|
204
|
-
</div>
|
|
205
|
-
) : (
|
|
206
|
-
<VideoThumbnail asset={asset} />
|
|
207
|
-
)}
|
|
208
|
-
</PlayButton>
|
|
209
|
-
)}
|
|
210
|
-
<VideoMetadata asset={asset} />
|
|
211
|
-
<div
|
|
212
|
-
style={{
|
|
213
|
-
display: 'flex',
|
|
214
|
-
width: '100%',
|
|
215
|
-
alignItems: 'flex-end',
|
|
216
|
-
justifyContent: 'flex-start',
|
|
217
|
-
gap: '.35rem',
|
|
218
|
-
}}
|
|
219
|
-
>
|
|
220
|
-
{onSelect && (
|
|
221
|
-
<Button
|
|
222
|
-
icon={CheckmarkIcon}
|
|
223
|
-
fontSize={2}
|
|
224
|
-
padding={2}
|
|
225
|
-
mode="ghost"
|
|
226
|
-
text="Select"
|
|
227
|
-
style={{flex: 1}}
|
|
228
|
-
tone="positive"
|
|
229
|
-
onClick={select}
|
|
230
|
-
/>
|
|
231
|
-
)}
|
|
232
|
-
<Button
|
|
233
|
-
icon={EditIcon}
|
|
234
|
-
fontSize={2}
|
|
235
|
-
padding={2}
|
|
236
|
-
mode="ghost"
|
|
237
|
-
text="Details"
|
|
238
|
-
style={{flex: 1}}
|
|
239
|
-
onClick={edit}
|
|
240
|
-
/>
|
|
241
|
-
</div>
|
|
242
|
-
</Stack>
|
|
243
|
-
</Card>
|
|
244
|
-
)
|
|
245
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import {CalendarIcon, ClockIcon, TagIcon} from '@sanity/icons'
|
|
2
|
-
import {Inline, Stack, Text} from '@sanity/ui'
|
|
3
|
-
|
|
4
|
-
import getVideoMetadata from '../util/getVideoMetadata'
|
|
5
|
-
import type {VideoAssetDocument} from '../util/types'
|
|
6
|
-
import IconInfo from './IconInfo'
|
|
7
|
-
|
|
8
|
-
const VideoMetadata = (props: {asset: VideoAssetDocument}) => {
|
|
9
|
-
if (!props.asset) {
|
|
10
|
-
return null
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const displayInfo = getVideoMetadata(props.asset)
|
|
14
|
-
return (
|
|
15
|
-
<Stack space={2}>
|
|
16
|
-
{displayInfo.title && (
|
|
17
|
-
<Text
|
|
18
|
-
size={1}
|
|
19
|
-
weight="semibold"
|
|
20
|
-
style={{
|
|
21
|
-
wordWrap: 'break-word',
|
|
22
|
-
}}
|
|
23
|
-
>
|
|
24
|
-
{displayInfo.title}
|
|
25
|
-
</Text>
|
|
26
|
-
)}
|
|
27
|
-
<Inline space={3}>
|
|
28
|
-
{displayInfo?.duration && (
|
|
29
|
-
<IconInfo text={displayInfo.duration} icon={ClockIcon} size={1} muted />
|
|
30
|
-
)}
|
|
31
|
-
<IconInfo
|
|
32
|
-
text={displayInfo.createdAt.toISOString().split('T')[0]!}
|
|
33
|
-
icon={CalendarIcon}
|
|
34
|
-
size={1}
|
|
35
|
-
muted
|
|
36
|
-
/>
|
|
37
|
-
{displayInfo.title != displayInfo.id.slice(0, 12) && (
|
|
38
|
-
<IconInfo text={displayInfo.id.slice(0, 12)} icon={TagIcon} size={1} muted />
|
|
39
|
-
)}
|
|
40
|
-
</Inline>
|
|
41
|
-
</Stack>
|
|
42
|
-
)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export default VideoMetadata
|
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
import {type MuxPlayerProps, type MuxPlayerRefAttributes} from '@mux/mux-player-react'
|
|
2
|
-
import MuxPlayer from '@mux/mux-player-react/lazy'
|
|
3
|
-
import {ErrorOutlineIcon} from '@sanity/icons'
|
|
4
|
-
import {Card, Text} from '@sanity/ui'
|
|
5
|
-
import {type PropsWithChildren, Suspense, useMemo, useRef, useState} from 'react'
|
|
6
|
-
|
|
7
|
-
import {useDialogStateContext} from '../context/DialogStateContext'
|
|
8
|
-
import {useClient} from '../hooks/useClient'
|
|
9
|
-
import {AUDIO_ASPECT_RATIO, MIN_ASPECT_RATIO} from '../util/constants'
|
|
10
|
-
import {generateJwt} from '../util/generateJwt'
|
|
11
|
-
import {getPlaybackId} from '../util/getPlaybackPolicy'
|
|
12
|
-
import {getPlaybackPolicyById} from '../util/getPlaybackPolicy'
|
|
13
|
-
import {getPosterSrc} from '../util/getPosterSrc'
|
|
14
|
-
import {getVideoSrc} from '../util/getVideoSrc'
|
|
15
|
-
import {tryWithSuspend} from '../util/tryWithSuspend'
|
|
16
|
-
import type {VideoAssetDocument} from '../util/types'
|
|
17
|
-
import CaptionsDialog from './CaptionsDialog'
|
|
18
|
-
import EditThumbnailDialog from './EditThumbnailDialog'
|
|
19
|
-
import {AudioIcon} from './icons/Audio'
|
|
20
|
-
|
|
21
|
-
export default function VideoPlayer({
|
|
22
|
-
asset,
|
|
23
|
-
thumbnailWidth = 250,
|
|
24
|
-
children,
|
|
25
|
-
hlsConfig,
|
|
26
|
-
...props
|
|
27
|
-
}: PropsWithChildren<
|
|
28
|
-
{
|
|
29
|
-
asset: VideoAssetDocument
|
|
30
|
-
thumbnailWidth?: number
|
|
31
|
-
forceAspectRatio?: number
|
|
32
|
-
hlsConfig?: MuxPlayerProps['_hlsConfig']
|
|
33
|
-
} & Partial<Pick<MuxPlayerProps, 'autoPlay'>>
|
|
34
|
-
>) {
|
|
35
|
-
const client = useClient()
|
|
36
|
-
const {dialogState} = useDialogStateContext()
|
|
37
|
-
|
|
38
|
-
const isAudio = assetIsAudio(asset)
|
|
39
|
-
const muxPlayer = useRef<MuxPlayerRefAttributes>(null)
|
|
40
|
-
const playerContainerRef = useRef<HTMLDivElement>(null)
|
|
41
|
-
const [error, setError] = useState<Error>()
|
|
42
|
-
|
|
43
|
-
/* Playback ID that will be used to play the video */
|
|
44
|
-
const playbackId = useMemo(() => {
|
|
45
|
-
try {
|
|
46
|
-
return getPlaybackId(asset, ['public', 'signed', 'drm'])
|
|
47
|
-
} catch (e) {
|
|
48
|
-
// oxlint-disable-next-line react/react-compiler
|
|
49
|
-
setError(new TypeError('Asset has no playback ID', {cause: e}))
|
|
50
|
-
return undefined
|
|
51
|
-
}
|
|
52
|
-
}, [asset])
|
|
53
|
-
|
|
54
|
-
const muxPlaybackId = useMemo(() => {
|
|
55
|
-
if (!playbackId) return undefined
|
|
56
|
-
return getPlaybackPolicyById(asset, playbackId)
|
|
57
|
-
}, [asset, playbackId])
|
|
58
|
-
|
|
59
|
-
const src = useMemo(() => {
|
|
60
|
-
if (!playbackId) return undefined
|
|
61
|
-
if (!muxPlaybackId) return undefined
|
|
62
|
-
return tryWithSuspend(
|
|
63
|
-
() => getVideoSrc({muxPlaybackId, client}),
|
|
64
|
-
(e: Error) => {
|
|
65
|
-
setError(e)
|
|
66
|
-
return undefined
|
|
67
|
-
},
|
|
68
|
-
)
|
|
69
|
-
}, [muxPlaybackId, playbackId, client])
|
|
70
|
-
|
|
71
|
-
const poster = useMemo(() => {
|
|
72
|
-
return tryWithSuspend(
|
|
73
|
-
() => getPosterSrc({asset, client, width: thumbnailWidth}),
|
|
74
|
-
(e: Error) => {
|
|
75
|
-
setError(e)
|
|
76
|
-
return undefined
|
|
77
|
-
},
|
|
78
|
-
)
|
|
79
|
-
}, [asset, client, thumbnailWidth])
|
|
80
|
-
|
|
81
|
-
const signedToken = useMemo(() => {
|
|
82
|
-
try {
|
|
83
|
-
const url = new URL(src!)
|
|
84
|
-
return url.searchParams.get('token')
|
|
85
|
-
} catch {
|
|
86
|
-
return undefined
|
|
87
|
-
}
|
|
88
|
-
}, [src])
|
|
89
|
-
const drmToken = useMemo(() => {
|
|
90
|
-
if (!playbackId) return undefined
|
|
91
|
-
if (muxPlaybackId?.policy !== 'drm') return undefined
|
|
92
|
-
|
|
93
|
-
return tryWithSuspend(
|
|
94
|
-
() => generateJwt(client, playbackId, 'd'),
|
|
95
|
-
(e: Error) => {
|
|
96
|
-
setError(e)
|
|
97
|
-
return undefined
|
|
98
|
-
},
|
|
99
|
-
)
|
|
100
|
-
}, [client, muxPlaybackId?.policy, playbackId])
|
|
101
|
-
const tokens:
|
|
102
|
-
| Partial<{
|
|
103
|
-
playback?: string
|
|
104
|
-
thumbnail?: string
|
|
105
|
-
storyboard?: string
|
|
106
|
-
drm?: string
|
|
107
|
-
}>
|
|
108
|
-
| undefined = useMemo(() => {
|
|
109
|
-
try {
|
|
110
|
-
const partialTokens: {
|
|
111
|
-
playback?: string
|
|
112
|
-
thumbnail?: string
|
|
113
|
-
storyboard?: string
|
|
114
|
-
drm?: string
|
|
115
|
-
} = {
|
|
116
|
-
playback: undefined,
|
|
117
|
-
thumbnail: undefined,
|
|
118
|
-
storyboard: undefined,
|
|
119
|
-
drm: undefined,
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (signedToken) {
|
|
123
|
-
partialTokens.playback = signedToken
|
|
124
|
-
partialTokens.thumbnail = signedToken
|
|
125
|
-
partialTokens.storyboard = signedToken
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (drmToken) {
|
|
129
|
-
partialTokens.drm = drmToken
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return {...partialTokens}
|
|
133
|
-
} catch {
|
|
134
|
-
return undefined
|
|
135
|
-
}
|
|
136
|
-
}, [signedToken, drmToken])
|
|
137
|
-
|
|
138
|
-
const [width, height] = (asset?.data?.aspect_ratio ?? '16:9').split(':').map(Number)
|
|
139
|
-
const targetAspectRatio =
|
|
140
|
-
props.forceAspectRatio || (Number.isNaN(width) ? 16 / 9 : width! / height!)
|
|
141
|
-
let aspectRatio = Math.max(MIN_ASPECT_RATIO, targetAspectRatio)
|
|
142
|
-
if (isAudio) {
|
|
143
|
-
aspectRatio = props.forceAspectRatio
|
|
144
|
-
? // Make it wider when forcing aspect ratio to balance with videos' rendering height (audio players overflow a bit)
|
|
145
|
-
props.forceAspectRatio * 1.2
|
|
146
|
-
: AUDIO_ASPECT_RATIO
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/* We use Suspense here because `generateJwt` and related functions use suspend()
|
|
150
|
-
under the hood */
|
|
151
|
-
return (
|
|
152
|
-
<>
|
|
153
|
-
<Card
|
|
154
|
-
ref={playerContainerRef}
|
|
155
|
-
tone="transparent"
|
|
156
|
-
style={{
|
|
157
|
-
aspectRatio: aspectRatio,
|
|
158
|
-
position: 'relative',
|
|
159
|
-
...(isAudio && {display: 'flex', alignItems: 'flex-end'}),
|
|
160
|
-
}}
|
|
161
|
-
>
|
|
162
|
-
{src && poster && (
|
|
163
|
-
<>
|
|
164
|
-
{isAudio && (
|
|
165
|
-
<AudioIcon
|
|
166
|
-
style={{
|
|
167
|
-
padding: '0.5em',
|
|
168
|
-
width: '2.2em',
|
|
169
|
-
height: '2.2em',
|
|
170
|
-
position: 'absolute',
|
|
171
|
-
top: 0,
|
|
172
|
-
left: 0,
|
|
173
|
-
zIndex: 1,
|
|
174
|
-
}}
|
|
175
|
-
/>
|
|
176
|
-
)}
|
|
177
|
-
<Suspense fallback={null}>
|
|
178
|
-
<MuxPlayer
|
|
179
|
-
poster={isAudio ? undefined : poster}
|
|
180
|
-
ref={muxPlayer}
|
|
181
|
-
{...props}
|
|
182
|
-
playsInline
|
|
183
|
-
playbackId={playbackId}
|
|
184
|
-
tokens={tokens}
|
|
185
|
-
preload="metadata"
|
|
186
|
-
crossOrigin="anonymous"
|
|
187
|
-
metadata={{
|
|
188
|
-
player_name: 'Sanity Admin Dashboard',
|
|
189
|
-
// @ts-expect-error - this constant is search/replaced so must be exact, not accessed with an index signature
|
|
190
|
-
player_version: process.env.PKG_VERSION,
|
|
191
|
-
page_type: 'Preview Player',
|
|
192
|
-
}}
|
|
193
|
-
audio={isAudio}
|
|
194
|
-
_hlsConfig={hlsConfig}
|
|
195
|
-
style={{
|
|
196
|
-
...(!isAudio && {height: '100%'}),
|
|
197
|
-
width: '100%',
|
|
198
|
-
display: 'block',
|
|
199
|
-
objectFit: 'contain',
|
|
200
|
-
...(isAudio && {alignSelf: 'end'}),
|
|
201
|
-
}}
|
|
202
|
-
/>
|
|
203
|
-
{children}
|
|
204
|
-
</Suspense>
|
|
205
|
-
</>
|
|
206
|
-
)}
|
|
207
|
-
{error ? (
|
|
208
|
-
<div
|
|
209
|
-
style={{
|
|
210
|
-
position: 'absolute',
|
|
211
|
-
top: '50%',
|
|
212
|
-
left: '50%',
|
|
213
|
-
transform: 'translate(-50%, -50%)',
|
|
214
|
-
}}
|
|
215
|
-
>
|
|
216
|
-
<Text muted>
|
|
217
|
-
<ErrorOutlineIcon style={{marginRight: '0.15em'}} />
|
|
218
|
-
{typeof error === 'object' && 'message' in error && typeof error.message === 'string'
|
|
219
|
-
? error.message
|
|
220
|
-
: 'Error loading video'}
|
|
221
|
-
</Text>
|
|
222
|
-
</div>
|
|
223
|
-
) : null}
|
|
224
|
-
{children}
|
|
225
|
-
</Card>
|
|
226
|
-
|
|
227
|
-
{dialogState === 'edit-thumbnail' && (
|
|
228
|
-
<EditThumbnailDialog
|
|
229
|
-
asset={asset}
|
|
230
|
-
// oxlint-disable-next-line react/react-compiler
|
|
231
|
-
currentTime={muxPlayer?.current?.currentTime}
|
|
232
|
-
/>
|
|
233
|
-
)}
|
|
234
|
-
{dialogState === 'edit-captions' && <CaptionsDialog asset={asset} />}
|
|
235
|
-
</>
|
|
236
|
-
)
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
export function assetIsAudio(asset: VideoAssetDocument) {
|
|
240
|
-
return asset.data?.max_stored_resolution === 'Audio only'
|
|
241
|
-
}
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import {ErrorOutlineIcon} from '@sanity/icons'
|
|
2
|
-
import {Box, Card, type CardTone, Spinner, Stack, Text} from '@sanity/ui'
|
|
3
|
-
import {Suspense, useMemo, useRef, useState} from 'react'
|
|
4
|
-
import {styled} from 'styled-components'
|
|
5
|
-
|
|
6
|
-
import {useClient} from '../hooks/useClient'
|
|
7
|
-
import {useInView} from '../hooks/useInView'
|
|
8
|
-
import {THUMBNAIL_ASPECT_RATIO} from '../util/constants'
|
|
9
|
-
import {getAnimatedPosterSrc} from '../util/getAnimatedPosterSrc'
|
|
10
|
-
import {getPosterSrc} from '../util/getPosterSrc'
|
|
11
|
-
import {tryWithSuspend} from '../util/tryWithSuspend'
|
|
12
|
-
import type {AssetThumbnailOptions, MuxAnimatedThumbnailUrl, MuxThumbnailUrl} from '../util/types'
|
|
13
|
-
|
|
14
|
-
const Image = styled.img`
|
|
15
|
-
transition: opacity 0.175s ease-out 0s;
|
|
16
|
-
display: block;
|
|
17
|
-
width: 100%;
|
|
18
|
-
height: 100%;
|
|
19
|
-
object-fit: contain;
|
|
20
|
-
object-position: center center;
|
|
21
|
-
`
|
|
22
|
-
|
|
23
|
-
type ImageStatus = 'loading' | 'error' | 'loaded'
|
|
24
|
-
|
|
25
|
-
const STATUS_TO_TONE: Record<ImageStatus, CardTone> = {
|
|
26
|
-
loading: 'transparent',
|
|
27
|
-
error: 'critical',
|
|
28
|
-
loaded: 'default',
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// oxlint-disable-next-line react/react-compiler
|
|
32
|
-
export default function VideoThumbnail({
|
|
33
|
-
asset,
|
|
34
|
-
width,
|
|
35
|
-
staticImage = false,
|
|
36
|
-
}: {
|
|
37
|
-
asset: AssetThumbnailOptions['asset']
|
|
38
|
-
width?: number
|
|
39
|
-
staticImage?: boolean
|
|
40
|
-
}) {
|
|
41
|
-
const posterWidth = width || 250
|
|
42
|
-
const client = useClient()
|
|
43
|
-
const ref = useRef<HTMLDivElement | null>(null)
|
|
44
|
-
const inView = useInView(ref)
|
|
45
|
-
|
|
46
|
-
const [status, setStatus] = useState<ImageStatus>('loading')
|
|
47
|
-
const [error, setError] = useState<string | null>(null)
|
|
48
|
-
|
|
49
|
-
const thumbnailSrc = useMemo(() => {
|
|
50
|
-
return tryWithSuspend(
|
|
51
|
-
() => {
|
|
52
|
-
let thumbnail: MuxAnimatedThumbnailUrl | MuxThumbnailUrl | undefined
|
|
53
|
-
|
|
54
|
-
if (staticImage) thumbnail = getPosterSrc({asset, client, width: posterWidth})
|
|
55
|
-
else thumbnail = getAnimatedPosterSrc({asset, client, width: posterWidth})
|
|
56
|
-
return thumbnail
|
|
57
|
-
},
|
|
58
|
-
(err: Error) => {
|
|
59
|
-
handleError(err.message)
|
|
60
|
-
return undefined
|
|
61
|
-
},
|
|
62
|
-
)
|
|
63
|
-
}, [asset, client, posterWidth, staticImage])
|
|
64
|
-
|
|
65
|
-
function handleLoad() {
|
|
66
|
-
setStatus('loaded')
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function handleError(err?: string) {
|
|
70
|
-
setStatus('error')
|
|
71
|
-
if (err) {
|
|
72
|
-
setError(err)
|
|
73
|
-
} else {
|
|
74
|
-
setError('Failed loading thumbnail')
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return (
|
|
79
|
-
<Suspense fallback={<span>Preparing thumbnail</span>}>
|
|
80
|
-
<Card
|
|
81
|
-
style={{
|
|
82
|
-
aspectRatio: THUMBNAIL_ASPECT_RATIO,
|
|
83
|
-
position: 'relative',
|
|
84
|
-
maxWidth: width ? `${width}px` : undefined,
|
|
85
|
-
width: '100%',
|
|
86
|
-
flex: 1,
|
|
87
|
-
}}
|
|
88
|
-
border
|
|
89
|
-
radius={2}
|
|
90
|
-
ref={ref}
|
|
91
|
-
tone={STATUS_TO_TONE[status]}
|
|
92
|
-
>
|
|
93
|
-
{inView ? (
|
|
94
|
-
<>
|
|
95
|
-
{status === 'loading' && (
|
|
96
|
-
<Box
|
|
97
|
-
style={{
|
|
98
|
-
position: 'absolute',
|
|
99
|
-
left: '50%',
|
|
100
|
-
top: '50%',
|
|
101
|
-
transform: 'translate(-50%, -50%)',
|
|
102
|
-
}}
|
|
103
|
-
>
|
|
104
|
-
<Spinner />
|
|
105
|
-
</Box>
|
|
106
|
-
)}
|
|
107
|
-
{status === 'error' && (
|
|
108
|
-
<Stack
|
|
109
|
-
space={4}
|
|
110
|
-
style={{
|
|
111
|
-
position: 'absolute',
|
|
112
|
-
width: '100%',
|
|
113
|
-
left: 0,
|
|
114
|
-
top: '50%',
|
|
115
|
-
transform: 'translateY(-50%)',
|
|
116
|
-
justifyItems: 'center',
|
|
117
|
-
}}
|
|
118
|
-
>
|
|
119
|
-
<Text size={4} muted>
|
|
120
|
-
<ErrorOutlineIcon style={{fontSize: '1.75em'}} />
|
|
121
|
-
</Text>
|
|
122
|
-
<Text muted align="center">
|
|
123
|
-
{error}
|
|
124
|
-
</Text>
|
|
125
|
-
</Stack>
|
|
126
|
-
)}
|
|
127
|
-
<Image
|
|
128
|
-
src={thumbnailSrc ?? undefined}
|
|
129
|
-
alt={`Preview for ${staticImage ? 'image' : 'video'} ${asset.filename || asset.assetId}`}
|
|
130
|
-
onLoad={handleLoad}
|
|
131
|
-
onError={() => handleError()}
|
|
132
|
-
style={{opacity: status === 'loaded' ? 1 : 0}}
|
|
133
|
-
/>
|
|
134
|
-
</>
|
|
135
|
-
) : null}
|
|
136
|
-
</Card>
|
|
137
|
-
</Suspense>
|
|
138
|
-
)
|
|
139
|
-
}
|