sanity-plugin-mux-input 2.14.0 → 2.15.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/README.md +25 -24
- package/dist/index.d.mts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +771 -351
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +773 -353
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/_exports/index.ts +1 -0
- package/src/actions/secrets.ts +6 -1
- package/src/actions/upload.ts +1 -1
- package/src/components/ConfigureApi.tsx +51 -5
- package/src/components/EditCaptionDialog.tsx +2 -2
- package/src/components/InputBrowser.tsx +8 -2
- package/src/components/PageSelector.tsx +4 -7
- package/src/components/Player.styled.tsx +7 -2
- package/src/components/PlayerActionsMenu.tsx +1 -1
- package/src/components/SelectAsset.tsx +9 -3
- package/src/components/StudioTool.tsx +2 -2
- package/src/components/UploadConfiguration.tsx +104 -343
- package/src/components/Uploader.tsx +18 -7
- package/src/components/VideoDetails/VideoDetails.tsx +28 -8
- package/src/components/VideoInBrowser.tsx +53 -6
- package/src/components/VideoPlayer.tsx +120 -47
- package/src/components/VideoThumbnail.tsx +84 -72
- package/src/components/VideosBrowser.tsx +7 -5
- package/src/components/uploadConfiguration/PlaybackPolicy.tsx +95 -6
- package/src/components/uploadConfiguration/PlaybackPolicyOption.tsx +26 -10
- package/src/components/uploadConfiguration/ResolutionTierSelector.tsx +71 -0
- package/src/components/uploadConfiguration/StaticRenditionSelector.tsx +179 -0
- package/src/context/DrmPlaybackWarningContext.tsx +93 -0
- package/src/hooks/useFetchFileSize.ts +54 -0
- package/src/hooks/useMediaMetadata.ts +100 -0
- package/src/hooks/useSaveSecrets.ts +10 -3
- package/src/hooks/useSecretsDocumentValues.ts +9 -1
- package/src/hooks/useSecretsFormState.ts +6 -3
- package/src/util/asserters.ts +14 -0
- package/src/util/createUrlParamsObject.ts +7 -3
- package/src/util/generateJwt.ts +11 -2
- package/src/util/getPlaybackPolicy.ts +63 -4
- package/src/util/getStoryboardSrc.ts +7 -3
- package/src/util/getVideoMetadata.ts +1 -0
- package/src/util/getVideoSrc.ts +9 -9
- package/src/util/readSecrets.ts +3 -1
- package/src/util/textTracks.ts +6 -3
- package/src/util/tryWithSuspend.ts +22 -0
- package/src/util/types.ts +27 -2
- package/src/util/getPlaybackId.ts +0 -9
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
import React, {useEffect, useState} from 'react'
|
|
28
28
|
|
|
29
29
|
import {DIALOGS_Z_INDEX} from '../../util/constants'
|
|
30
|
-
import
|
|
30
|
+
import {MuxPlaybackId, MuxTextTrack, PlaybackPolicy} from '../../util/types'
|
|
31
31
|
import FormField from '../FormField'
|
|
32
32
|
import IconInfo from '../IconInfo'
|
|
33
33
|
import {ResolutionIcon} from '../icons/Resolution'
|
|
@@ -295,13 +295,7 @@ const VideoDetails: React.FC<VideoDetailsProps> = (props) => {
|
|
|
295
295
|
size={2}
|
|
296
296
|
/>
|
|
297
297
|
<IconInfo text={`Mux ID: \n${displayInfo.id}`} icon={TagIcon} size={2} />
|
|
298
|
-
{displayInfo
|
|
299
|
-
<IconInfo
|
|
300
|
-
text={`Playback ID: ${displayInfo.playbackId}`}
|
|
301
|
-
icon={TagIcon}
|
|
302
|
-
size={2}
|
|
303
|
-
/>
|
|
304
|
-
)}
|
|
298
|
+
<PlaybackIds playback_ids={displayInfo.playback_ids} />
|
|
305
299
|
</Stack>
|
|
306
300
|
</Stack>
|
|
307
301
|
</TabPanel>
|
|
@@ -319,4 +313,30 @@ const VideoDetails: React.FC<VideoDetailsProps> = (props) => {
|
|
|
319
313
|
)
|
|
320
314
|
}
|
|
321
315
|
|
|
316
|
+
const PlaybackIds = ({playback_ids}: {playback_ids?: MuxPlaybackId[]}) => {
|
|
317
|
+
if (playback_ids) {
|
|
318
|
+
return playback_ids.map((entry) => (
|
|
319
|
+
<IconInfo
|
|
320
|
+
key={entry.id}
|
|
321
|
+
text={`Playback ID [${policyToText(entry.policy)}]: ${entry.id}`}
|
|
322
|
+
icon={TagIcon}
|
|
323
|
+
size={2}
|
|
324
|
+
/>
|
|
325
|
+
))
|
|
326
|
+
}
|
|
327
|
+
return <IconInfo text={'No Playback ID'} icon={TagIcon} size={2} />
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const policyToText = (policy: PlaybackPolicy) => {
|
|
331
|
+
switch (policy) {
|
|
332
|
+
case 'drm':
|
|
333
|
+
return 'DRM'
|
|
334
|
+
case 'signed':
|
|
335
|
+
return 'Signed'
|
|
336
|
+
case 'public':
|
|
337
|
+
return 'Public'
|
|
338
|
+
default:
|
|
339
|
+
return policy
|
|
340
|
+
}
|
|
341
|
+
}
|
|
322
342
|
export default VideoDetails
|
|
@@ -3,6 +3,7 @@ import {Button, Card, Stack, Text, Tooltip} from '@sanity/ui'
|
|
|
3
3
|
import React, {useState} from 'react'
|
|
4
4
|
import {styled} from 'styled-components'
|
|
5
5
|
|
|
6
|
+
import {DRMWarningDialog, useDrmPlaybackWarningContext} from '../context/DrmPlaybackWarningContext'
|
|
6
7
|
import {THUMBNAIL_ASPECT_RATIO} from '../util/constants'
|
|
7
8
|
import {getPlaybackPolicy} from '../util/getPlaybackPolicy'
|
|
8
9
|
import {VideoAssetDocument} from '../util/types'
|
|
@@ -71,6 +72,8 @@ const PlayButton = styled.button`
|
|
|
71
72
|
}
|
|
72
73
|
`
|
|
73
74
|
|
|
75
|
+
type RenderState = 'render-video' | 'pre-render-warn' | false
|
|
76
|
+
|
|
74
77
|
export default function VideoInBrowser({
|
|
75
78
|
onSelect,
|
|
76
79
|
onEdit,
|
|
@@ -80,16 +83,23 @@ export default function VideoInBrowser({
|
|
|
80
83
|
onEdit?: (asset: VideoAssetDocument) => void
|
|
81
84
|
asset: VideoAssetDocument
|
|
82
85
|
}) {
|
|
83
|
-
const [renderVideo, setRenderVideo] = useState(false)
|
|
86
|
+
const [renderVideo, setRenderVideo] = useState<RenderState>(false)
|
|
84
87
|
const select = React.useCallback(() => onSelect?.(asset), [onSelect, asset])
|
|
85
88
|
const edit = React.useCallback(() => onEdit?.(asset), [onEdit, asset])
|
|
89
|
+
const {hasShownWarning} = useDrmPlaybackWarningContext()
|
|
86
90
|
|
|
87
91
|
if (!asset) {
|
|
88
92
|
return null
|
|
89
93
|
}
|
|
90
94
|
|
|
91
95
|
const playbackPolicy = getPlaybackPolicy(asset)
|
|
92
|
-
|
|
96
|
+
const onClickPlay = () => {
|
|
97
|
+
if (playbackPolicy?.policy === 'drm' && !hasShownWarning) {
|
|
98
|
+
setRenderVideo('pre-render-warn')
|
|
99
|
+
} else {
|
|
100
|
+
setRenderVideo('render-video')
|
|
101
|
+
}
|
|
102
|
+
}
|
|
93
103
|
return (
|
|
94
104
|
<Card
|
|
95
105
|
border
|
|
@@ -100,7 +110,7 @@ export default function VideoInBrowser({
|
|
|
100
110
|
position: 'relative',
|
|
101
111
|
}}
|
|
102
112
|
>
|
|
103
|
-
{playbackPolicy === 'signed' && (
|
|
113
|
+
{playbackPolicy?.policy === 'signed' && (
|
|
104
114
|
<Tooltip
|
|
105
115
|
animate
|
|
106
116
|
content={
|
|
@@ -119,7 +129,7 @@ export default function VideoInBrowser({
|
|
|
119
129
|
position: 'absolute',
|
|
120
130
|
left: '1em',
|
|
121
131
|
top: '1em',
|
|
122
|
-
zIndex:
|
|
132
|
+
zIndex: 11,
|
|
123
133
|
}}
|
|
124
134
|
padding={2}
|
|
125
135
|
border
|
|
@@ -130,6 +140,36 @@ export default function VideoInBrowser({
|
|
|
130
140
|
</Card>
|
|
131
141
|
</Tooltip>
|
|
132
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
|
+
)}
|
|
133
173
|
<Stack
|
|
134
174
|
space={3}
|
|
135
175
|
height="fill"
|
|
@@ -137,10 +177,17 @@ export default function VideoInBrowser({
|
|
|
137
177
|
gridTemplateRows: 'min-content min-content 1fr',
|
|
138
178
|
}}
|
|
139
179
|
>
|
|
140
|
-
{renderVideo
|
|
180
|
+
{renderVideo === 'pre-render-warn' && (
|
|
181
|
+
<DRMWarningDialog
|
|
182
|
+
onClose={() => {
|
|
183
|
+
setRenderVideo('render-video')
|
|
184
|
+
}}
|
|
185
|
+
/>
|
|
186
|
+
)}
|
|
187
|
+
{renderVideo === 'render-video' ? (
|
|
141
188
|
<VideoPlayer asset={asset} autoPlay forceAspectRatio={THUMBNAIL_ASPECT_RATIO} />
|
|
142
189
|
) : (
|
|
143
|
-
<PlayButton onClick={
|
|
190
|
+
<PlayButton onClick={onClickPlay}>
|
|
144
191
|
<div data-play>
|
|
145
192
|
<PlayIcon />
|
|
146
193
|
</div>
|
|
@@ -2,13 +2,17 @@ import {type MuxPlayerProps, type MuxPlayerRefAttributes} from '@mux/mux-player-
|
|
|
2
2
|
import MuxPlayer from '@mux/mux-player-react/lazy'
|
|
3
3
|
import {ErrorOutlineIcon} from '@sanity/icons'
|
|
4
4
|
import {Card, Text} from '@sanity/ui'
|
|
5
|
-
import {type PropsWithChildren, useMemo, useRef} from 'react'
|
|
5
|
+
import {type PropsWithChildren, Suspense, useMemo, useRef, useState} from 'react'
|
|
6
6
|
|
|
7
7
|
import {useDialogStateContext} from '../context/DialogStateContext'
|
|
8
8
|
import {useClient} from '../hooks/useClient'
|
|
9
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'
|
|
10
13
|
import {getPosterSrc} from '../util/getPosterSrc'
|
|
11
14
|
import {getVideoSrc} from '../util/getVideoSrc'
|
|
15
|
+
import {tryWithSuspend} from '../util/tryWithSuspend'
|
|
12
16
|
import type {VideoAssetDocument} from '../util/types'
|
|
13
17
|
import CaptionsDialog from './CaptionsDialog'
|
|
14
18
|
import EditThumbnailDialog from './EditThumbnailDialog'
|
|
@@ -33,32 +37,101 @@ export default function VideoPlayer({
|
|
|
33
37
|
|
|
34
38
|
const isAudio = assetIsAudio(asset)
|
|
35
39
|
const muxPlayer = useRef<MuxPlayerRefAttributes>(null)
|
|
40
|
+
const [error, setError] = useState<Error>()
|
|
36
41
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
thumbnail: thumbnailSrc,
|
|
40
|
-
error,
|
|
41
|
-
} = useMemo(() => {
|
|
42
|
+
/* Playback ID that will be used to play the video */
|
|
43
|
+
const playbackId = useMemo(() => {
|
|
42
44
|
try {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return {error: new TypeError('Asset has no playback ID')}
|
|
48
|
-
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
49
|
-
} catch (error) {
|
|
50
|
-
return {error}
|
|
45
|
+
return getPlaybackId(asset, ['public', 'signed', 'drm'])
|
|
46
|
+
} catch (e) {
|
|
47
|
+
setError(new TypeError('Asset has no playback ID'))
|
|
48
|
+
return undefined
|
|
51
49
|
}
|
|
50
|
+
}, [asset])
|
|
51
|
+
|
|
52
|
+
const muxPlaybackId = useMemo(() => {
|
|
53
|
+
if (!playbackId) return undefined
|
|
54
|
+
return getPlaybackPolicyById(asset, playbackId)
|
|
55
|
+
}, [asset, playbackId])
|
|
56
|
+
|
|
57
|
+
const src = useMemo(() => {
|
|
58
|
+
if (!playbackId) return undefined
|
|
59
|
+
if (!muxPlaybackId) return undefined
|
|
60
|
+
return tryWithSuspend(
|
|
61
|
+
() => getVideoSrc({muxPlaybackId, client}),
|
|
62
|
+
(e: Error) => {
|
|
63
|
+
setError(e)
|
|
64
|
+
return undefined
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
}, [muxPlaybackId, playbackId, client])
|
|
68
|
+
|
|
69
|
+
const poster = useMemo(() => {
|
|
70
|
+
return tryWithSuspend(
|
|
71
|
+
() => getPosterSrc({asset, client, width: thumbnailWidth}),
|
|
72
|
+
(e: Error) => {
|
|
73
|
+
setError(e)
|
|
74
|
+
return undefined
|
|
75
|
+
}
|
|
76
|
+
)
|
|
52
77
|
}, [asset, client, thumbnailWidth])
|
|
53
78
|
|
|
54
79
|
const signedToken = useMemo(() => {
|
|
55
80
|
try {
|
|
56
|
-
const url = new URL(
|
|
81
|
+
const url = new URL(src!)
|
|
57
82
|
return url.searchParams.get('token')
|
|
58
83
|
} catch {
|
|
59
|
-
return
|
|
84
|
+
return undefined
|
|
85
|
+
}
|
|
86
|
+
}, [src])
|
|
87
|
+
const drmToken = useMemo(() => {
|
|
88
|
+
if (!playbackId) return undefined
|
|
89
|
+
if (muxPlaybackId?.policy !== 'drm') return undefined
|
|
90
|
+
|
|
91
|
+
return tryWithSuspend(
|
|
92
|
+
() => generateJwt(client, playbackId, 'd'),
|
|
93
|
+
(e: Error) => {
|
|
94
|
+
setError(e)
|
|
95
|
+
return undefined
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
}, [client, muxPlaybackId?.policy, playbackId])
|
|
99
|
+
const tokens:
|
|
100
|
+
| Partial<{
|
|
101
|
+
playback?: string
|
|
102
|
+
thumbnail?: string
|
|
103
|
+
storyboard?: string
|
|
104
|
+
drm?: string
|
|
105
|
+
}>
|
|
106
|
+
| undefined = useMemo(() => {
|
|
107
|
+
try {
|
|
108
|
+
const partialTokens: {
|
|
109
|
+
playback?: string
|
|
110
|
+
thumbnail?: string
|
|
111
|
+
storyboard?: string
|
|
112
|
+
drm?: string
|
|
113
|
+
} = {
|
|
114
|
+
playback: undefined,
|
|
115
|
+
thumbnail: undefined,
|
|
116
|
+
storyboard: undefined,
|
|
117
|
+
drm: undefined,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (signedToken) {
|
|
121
|
+
partialTokens.playback = signedToken
|
|
122
|
+
partialTokens.thumbnail = signedToken
|
|
123
|
+
partialTokens.storyboard = signedToken
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (drmToken) {
|
|
127
|
+
partialTokens.drm = drmToken
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {...partialTokens}
|
|
131
|
+
} catch {
|
|
132
|
+
return undefined
|
|
60
133
|
}
|
|
61
|
-
}, [
|
|
134
|
+
}, [signedToken, drmToken])
|
|
62
135
|
|
|
63
136
|
const [width, height] = (asset?.data?.aspect_ratio ?? '16:9').split(':').map(Number)
|
|
64
137
|
const targetAspectRatio =
|
|
@@ -71,6 +144,8 @@ export default function VideoPlayer({
|
|
|
71
144
|
: AUDIO_ASPECT_RATIO
|
|
72
145
|
}
|
|
73
146
|
|
|
147
|
+
/* We use Suspense here because `generateJwt` and related functions use suspend()
|
|
148
|
+
under the hood */
|
|
74
149
|
return (
|
|
75
150
|
<>
|
|
76
151
|
<Card
|
|
@@ -81,7 +156,7 @@ export default function VideoPlayer({
|
|
|
81
156
|
...(isAudio && {display: 'flex', alignItems: 'flex-end'}),
|
|
82
157
|
}}
|
|
83
158
|
>
|
|
84
|
-
{
|
|
159
|
+
{src && poster && (
|
|
85
160
|
<>
|
|
86
161
|
{isAudio && (
|
|
87
162
|
<AudioIcon
|
|
@@ -96,35 +171,33 @@ export default function VideoPlayer({
|
|
|
96
171
|
}}
|
|
97
172
|
/>
|
|
98
173
|
)}
|
|
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
|
-
{children}
|
|
174
|
+
<Suspense fallback={null}>
|
|
175
|
+
<MuxPlayer
|
|
176
|
+
poster={isAudio ? undefined : poster}
|
|
177
|
+
ref={muxPlayer}
|
|
178
|
+
{...props}
|
|
179
|
+
playsInline
|
|
180
|
+
playbackId={playbackId}
|
|
181
|
+
tokens={tokens}
|
|
182
|
+
preload="metadata"
|
|
183
|
+
crossOrigin="anonymous"
|
|
184
|
+
metadata={{
|
|
185
|
+
player_name: 'Sanity Admin Dashboard',
|
|
186
|
+
player_version: process.env.PKG_VERSION,
|
|
187
|
+
page_type: 'Preview Player',
|
|
188
|
+
}}
|
|
189
|
+
audio={isAudio}
|
|
190
|
+
_hlsConfig={hlsConfig}
|
|
191
|
+
style={{
|
|
192
|
+
...(!isAudio && {height: '100%'}),
|
|
193
|
+
width: '100%',
|
|
194
|
+
display: 'block',
|
|
195
|
+
objectFit: 'contain',
|
|
196
|
+
...(isAudio && {alignSelf: 'end'}),
|
|
197
|
+
}}
|
|
198
|
+
/>
|
|
199
|
+
{children}
|
|
200
|
+
</Suspense>
|
|
128
201
|
</>
|
|
129
202
|
)}
|
|
130
203
|
{error ? (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {ErrorOutlineIcon} from '@sanity/icons'
|
|
2
2
|
import {Box, Card, CardTone, Spinner, Stack, Text} from '@sanity/ui'
|
|
3
|
-
import {useMemo, useRef, useState} from 'react'
|
|
3
|
+
import {Suspense, useMemo, useRef, useState} from 'react'
|
|
4
4
|
import {styled} from 'styled-components'
|
|
5
5
|
|
|
6
6
|
import {useClient} from '../hooks/useClient'
|
|
@@ -8,6 +8,7 @@ import {useInView} from '../hooks/useInView'
|
|
|
8
8
|
import {THUMBNAIL_ASPECT_RATIO} from '../util/constants'
|
|
9
9
|
import {getAnimatedPosterSrc} from '../util/getAnimatedPosterSrc'
|
|
10
10
|
import {getPosterSrc} from '../util/getPosterSrc'
|
|
11
|
+
import {tryWithSuspend} from '../util/tryWithSuspend'
|
|
11
12
|
import {AssetThumbnailOptions, MuxAnimatedThumbnailUrl, MuxThumbnailUrl} from '../util/types'
|
|
12
13
|
|
|
13
14
|
const Image = styled.img`
|
|
@@ -36,91 +37,102 @@ export default function VideoThumbnail({
|
|
|
36
37
|
width?: number
|
|
37
38
|
staticImage?: boolean
|
|
38
39
|
}) {
|
|
40
|
+
const posterWidth = width || 250
|
|
41
|
+
const client = useClient()
|
|
39
42
|
const ref = useRef<HTMLDivElement | null>(null)
|
|
40
43
|
const inView = useInView(ref)
|
|
41
|
-
const posterWidth = width || 250
|
|
42
44
|
|
|
43
45
|
const [status, setStatus] = useState<ImageStatus>('loading')
|
|
44
|
-
const
|
|
46
|
+
const [error, setError] = useState<string | null>(null)
|
|
45
47
|
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
else thumbnail = getAnimatedPosterSrc({asset, client, width: posterWidth})
|
|
48
|
+
const thumbnailSrc = useMemo(() => {
|
|
49
|
+
return tryWithSuspend(
|
|
50
|
+
() => {
|
|
51
|
+
let thumbnail: MuxAnimatedThumbnailUrl | MuxThumbnailUrl | undefined
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
if (staticImage) thumbnail = getPosterSrc({asset, client, width: posterWidth})
|
|
54
|
+
else thumbnail = getAnimatedPosterSrc({asset, client, width: posterWidth})
|
|
55
|
+
return thumbnail
|
|
56
|
+
},
|
|
57
|
+
(err: Error) => {
|
|
58
|
+
handleError(err.message)
|
|
59
|
+
return undefined
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
}, [asset, client, posterWidth, staticImage])
|
|
58
63
|
|
|
59
64
|
function handleLoad() {
|
|
60
65
|
setStatus('loaded')
|
|
61
66
|
}
|
|
62
67
|
|
|
63
|
-
function handleError() {
|
|
68
|
+
function handleError(err?: string) {
|
|
64
69
|
setStatus('error')
|
|
70
|
+
if (err) {
|
|
71
|
+
setError(err)
|
|
72
|
+
} else {
|
|
73
|
+
setError('Failed loading thumbnail')
|
|
74
|
+
}
|
|
65
75
|
}
|
|
66
76
|
|
|
67
77
|
return (
|
|
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
|
-
|
|
78
|
+
<Suspense fallback={<span>Preparing thumbnail</span>}>
|
|
79
|
+
<Card
|
|
80
|
+
style={{
|
|
81
|
+
aspectRatio: THUMBNAIL_ASPECT_RATIO,
|
|
82
|
+
position: 'relative',
|
|
83
|
+
maxWidth: width ? `${width}px` : undefined,
|
|
84
|
+
width: '100%',
|
|
85
|
+
flex: 1,
|
|
86
|
+
}}
|
|
87
|
+
border
|
|
88
|
+
radius={2}
|
|
89
|
+
ref={ref}
|
|
90
|
+
tone={STATUS_TO_TONE[status]}
|
|
91
|
+
>
|
|
92
|
+
{inView ? (
|
|
93
|
+
<>
|
|
94
|
+
{status === 'loading' && (
|
|
95
|
+
<Box
|
|
96
|
+
style={{
|
|
97
|
+
position: 'absolute',
|
|
98
|
+
left: '50%',
|
|
99
|
+
top: '50%',
|
|
100
|
+
transform: 'translate(-50%, -50%)',
|
|
101
|
+
}}
|
|
102
|
+
>
|
|
103
|
+
<Spinner />
|
|
104
|
+
</Box>
|
|
105
|
+
)}
|
|
106
|
+
{status === 'error' && (
|
|
107
|
+
<Stack
|
|
108
|
+
space={4}
|
|
109
|
+
style={{
|
|
110
|
+
position: 'absolute',
|
|
111
|
+
width: '100%',
|
|
112
|
+
left: 0,
|
|
113
|
+
top: '50%',
|
|
114
|
+
transform: 'translateY(-50%)',
|
|
115
|
+
justifyItems: 'center',
|
|
116
|
+
}}
|
|
117
|
+
>
|
|
118
|
+
<Text size={4} muted>
|
|
119
|
+
<ErrorOutlineIcon style={{fontSize: '1.75em'}} />
|
|
120
|
+
</Text>
|
|
121
|
+
<Text muted align="center">
|
|
122
|
+
{error}
|
|
123
|
+
</Text>
|
|
124
|
+
</Stack>
|
|
125
|
+
)}
|
|
126
|
+
<Image
|
|
127
|
+
src={thumbnailSrc ?? undefined}
|
|
128
|
+
alt={`Preview for ${staticImage ? 'image' : 'video'} ${asset.filename || asset.assetId}`}
|
|
129
|
+
onLoad={handleLoad}
|
|
130
|
+
onError={() => handleError()}
|
|
131
|
+
style={{opacity: status === 'loaded' ? 1 : 0}}
|
|
132
|
+
/>
|
|
133
|
+
</>
|
|
134
|
+
) : null}
|
|
135
|
+
</Card>
|
|
136
|
+
</Suspense>
|
|
125
137
|
)
|
|
126
138
|
}
|
|
@@ -2,8 +2,9 @@ import {SearchIcon} from '@sanity/icons'
|
|
|
2
2
|
import {Card, Flex, Grid, Inline, Label, Stack, Text, TextInput} from '@sanity/ui'
|
|
3
3
|
import {useMemo, useState} from 'react'
|
|
4
4
|
|
|
5
|
+
import {DrmPlaybackWarningContextProvider} from '../context/DrmPlaybackWarningContext'
|
|
5
6
|
import useAssets from '../hooks/useAssets'
|
|
6
|
-
import type {VideoAssetDocument} from '../util/types'
|
|
7
|
+
import type {PluginConfig, VideoAssetDocument} from '../util/types'
|
|
7
8
|
import ConfigureApi from './ConfigureApi'
|
|
8
9
|
import ImportVideosFromMux from './ImportVideosFromMux'
|
|
9
10
|
import PageSelector from './PageSelector'
|
|
@@ -15,10 +16,11 @@ import VideoDetails from './VideoDetails/VideoDetails'
|
|
|
15
16
|
import VideoInBrowser from './VideoInBrowser'
|
|
16
17
|
|
|
17
18
|
export interface VideosBrowserProps {
|
|
19
|
+
config: PluginConfig
|
|
18
20
|
onSelect?: (asset: VideoAssetDocument) => void
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
export default function VideosBrowser({onSelect}: VideosBrowserProps) {
|
|
23
|
+
export default function VideosBrowser({onSelect, config}: VideosBrowserProps) {
|
|
22
24
|
const {assets, isLoading, searchQuery, setSearchQuery, setSort, sort} = useAssets()
|
|
23
25
|
const [page, setPage] = useState<number>(0)
|
|
24
26
|
const pageLimit = 20
|
|
@@ -34,7 +36,7 @@ export default function VideosBrowser({onSelect}: VideosBrowserProps) {
|
|
|
34
36
|
|
|
35
37
|
const placement = onSelect ? 'input' : 'tool'
|
|
36
38
|
return (
|
|
37
|
-
|
|
39
|
+
<DrmPlaybackWarningContextProvider config={config}>
|
|
38
40
|
<Stack padding={4} space={4} style={{minHeight: '50vh'}}>
|
|
39
41
|
<Flex justify="space-between" align="center">
|
|
40
42
|
<Flex align="center" gap={3}>
|
|
@@ -47,7 +49,7 @@ export default function VideosBrowser({onSelect}: VideosBrowserProps) {
|
|
|
47
49
|
placeholder="Search videos"
|
|
48
50
|
/>
|
|
49
51
|
<SelectSortOptions setSort={setSort} sort={sort} />
|
|
50
|
-
<PageSelector page={page} setPage={setPage} total={pageTotal}
|
|
52
|
+
<PageSelector page={page} setPage={setPage} total={pageTotal} />
|
|
51
53
|
</Flex>
|
|
52
54
|
{placement === 'tool' && (
|
|
53
55
|
<Inline space={2}>
|
|
@@ -93,6 +95,6 @@ export default function VideosBrowser({onSelect}: VideosBrowserProps) {
|
|
|
93
95
|
{freshEditedAsset && (
|
|
94
96
|
<VideoDetails closeDialog={() => setEditedAsset(null)} asset={freshEditedAsset} />
|
|
95
97
|
)}
|
|
96
|
-
|
|
98
|
+
</DrmPlaybackWarningContextProvider>
|
|
97
99
|
)
|
|
98
100
|
}
|