sanity-plugin-mux-input 2.13.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 +35 -2
- package/dist/index.d.ts +35 -2
- package/dist/index.js +2176 -461
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2178 -463
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/_exports/index.ts +1 -0
- package/src/actions/assets.ts +75 -0
- package/src/actions/secrets.ts +6 -1
- package/src/actions/upload.ts +1 -1
- package/src/components/AddCaptionDialog.tsx +421 -0
- package/src/components/CaptionsDialog.tsx +23 -0
- package/src/components/ConfigureApi.tsx +51 -5
- package/src/components/EditCaptionDialog.tsx +508 -0
- package/src/components/InputBrowser.tsx +8 -2
- package/src/components/Onboard.tsx +2 -2
- package/src/components/PageSelector.tsx +54 -0
- package/src/components/Player.styled.tsx +7 -2
- package/src/components/PlayerActionsMenu.tsx +14 -6
- package/src/components/SelectAsset.tsx +9 -3
- package/src/components/StudioTool.tsx +2 -2
- package/src/components/TextTracksManager.tsx +781 -0
- package/src/components/UploadConfiguration.tsx +104 -343
- package/src/components/Uploader.styled.tsx +8 -15
- package/src/components/Uploader.tsx +25 -7
- package/src/components/VideoDetails/VideoDetails.tsx +43 -7
- package/src/components/VideoInBrowser.tsx +53 -6
- package/src/components/VideoPlayer.tsx +122 -47
- package/src/components/VideoThumbnail.tsx +84 -72
- package/src/components/VideosBrowser.tsx +15 -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/useAccessControl.ts +1 -0
- package/src/hooks/useDialogState.ts +1 -1
- 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 +4 -1
- package/src/util/getVideoSrc.ts +9 -9
- package/src/util/readSecrets.ts +3 -1
- package/src/util/textTracks.ts +222 -0
- package/src/util/tryWithSuspend.ts +22 -0
- package/src/util/types.ts +39 -6
- package/src/util/getPlaybackId.ts +0 -9
|
@@ -9,8 +9,9 @@ import {PatchEvent, set, setIfMissing} from 'sanity'
|
|
|
9
9
|
import {uploadFile, uploadUrl} from '../actions/upload'
|
|
10
10
|
import {DialogStateProvider} from '../context/DialogStateContext'
|
|
11
11
|
import {type DialogState, type SetDialogState} from '../hooks/useDialogState'
|
|
12
|
-
import {isValidUrl} from '../util/asserters'
|
|
12
|
+
import {isServerError, isValidUrl} from '../util/asserters'
|
|
13
13
|
import {extractDroppedFiles} from '../util/extractFiles'
|
|
14
|
+
import {hasPlaybackPolicy} from '../util/getPlaybackPolicy'
|
|
14
15
|
import type {
|
|
15
16
|
MuxInputProps,
|
|
16
17
|
MuxNewAssetSettings,
|
|
@@ -66,7 +67,7 @@ type UploaderStateAction =
|
|
|
66
67
|
| Extract<UploadUrlEvent, {type: 'url'}>
|
|
67
68
|
))
|
|
68
69
|
| {action: 'progress'; percent: number}
|
|
69
|
-
| {action: 'error'; error: Error}
|
|
70
|
+
| {action: 'error'; error: Error; settings: MuxNewAssetSettings}
|
|
70
71
|
| {action: 'complete' | 'reset'}
|
|
71
72
|
|
|
72
73
|
/**
|
|
@@ -125,12 +126,21 @@ export default function Uploader(props: Props) {
|
|
|
125
126
|
uploadRef.current = null
|
|
126
127
|
uploadingDocumentId.current = null
|
|
127
128
|
return INITIAL_STATE
|
|
128
|
-
case 'error':
|
|
129
|
+
case 'error': {
|
|
129
130
|
// Clear upload observable on error
|
|
130
131
|
uploadRef.current?.unsubscribe()
|
|
131
132
|
uploadRef.current = null
|
|
132
133
|
uploadingDocumentId.current = null
|
|
133
|
-
|
|
134
|
+
|
|
135
|
+
let error = action.error
|
|
136
|
+
if (isServerError(action.error) && hasPlaybackPolicy(action.settings, 'drm')) {
|
|
137
|
+
error = new Error(
|
|
138
|
+
'Unknown Error while uploading DRM protected content. Make sure your DRM configuration ID is valid and set correctly'
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return Object.assign({}, INITIAL_STATE, {error: error})
|
|
143
|
+
}
|
|
134
144
|
default:
|
|
135
145
|
return prev
|
|
136
146
|
}
|
|
@@ -254,7 +264,7 @@ export default function Uploader(props: Props) {
|
|
|
254
264
|
}
|
|
255
265
|
},
|
|
256
266
|
complete: () => dispatch({action: 'complete'}),
|
|
257
|
-
error: (error) => dispatch({action: 'error', error}),
|
|
267
|
+
error: (error) => dispatch({action: 'error', error, settings}),
|
|
258
268
|
})
|
|
259
269
|
}
|
|
260
270
|
|
|
@@ -297,6 +307,13 @@ export default function Uploader(props: Props) {
|
|
|
297
307
|
|
|
298
308
|
// Stages and validates an upload from pasting an asset URL
|
|
299
309
|
const handlePaste: React.ClipboardEventHandler<HTMLInputElement> = (event) => {
|
|
310
|
+
const target = event.target as HTMLElement
|
|
311
|
+
|
|
312
|
+
// Ignore paste coming from the VTT URL input
|
|
313
|
+
if (target.closest('#vtt-url')) {
|
|
314
|
+
return
|
|
315
|
+
}
|
|
316
|
+
|
|
300
317
|
event.preventDefault()
|
|
301
318
|
event.stopPropagation()
|
|
302
319
|
const clipboardData =
|
|
@@ -365,7 +382,7 @@ export default function Uploader(props: Props) {
|
|
|
365
382
|
|
|
366
383
|
// Upload has errored
|
|
367
384
|
if (state.error !== null) {
|
|
368
|
-
const error =
|
|
385
|
+
const error = state.error
|
|
369
386
|
return (
|
|
370
387
|
<Flex gap={3} direction="column" justify="center" align="center">
|
|
371
388
|
<Text size={5} muted>
|
|
@@ -373,7 +390,7 @@ export default function Uploader(props: Props) {
|
|
|
373
390
|
</Text>
|
|
374
391
|
<Text>Something went wrong</Text>
|
|
375
392
|
{error instanceof Error && error.message && (
|
|
376
|
-
<Text size={1} muted>
|
|
393
|
+
<Text size={1} muted weight="semibold" style={{textAlign: 'center'}}>
|
|
377
394
|
{error.message}
|
|
378
395
|
</Text>
|
|
379
396
|
)}
|
|
@@ -464,6 +481,7 @@ export default function Uploader(props: Props) {
|
|
|
464
481
|
</UploadCard>
|
|
465
482
|
{props.dialogState === 'select-video' && (
|
|
466
483
|
<InputBrowser
|
|
484
|
+
config={props.config}
|
|
467
485
|
asset={props.asset}
|
|
468
486
|
onChange={props.onChange}
|
|
469
487
|
setDialogState={props.setDialogState}
|
|
@@ -27,10 +27,12 @@ import {
|
|
|
27
27
|
import React, {useEffect, useState} from 'react'
|
|
28
28
|
|
|
29
29
|
import {DIALOGS_Z_INDEX} from '../../util/constants'
|
|
30
|
+
import {MuxPlaybackId, MuxTextTrack, PlaybackPolicy} from '../../util/types'
|
|
30
31
|
import FormField from '../FormField'
|
|
31
32
|
import IconInfo from '../IconInfo'
|
|
32
33
|
import {ResolutionIcon} from '../icons/Resolution'
|
|
33
34
|
import {StopWatchIcon} from '../icons/StopWatch'
|
|
35
|
+
import TextTracksManager from '../TextTracksManager'
|
|
34
36
|
import VideoPlayer from '../VideoPlayer'
|
|
35
37
|
import DeleteDialog from './DeleteDialog'
|
|
36
38
|
import useVideoDetails, {VideoDetailsProps} from './useVideoDetails'
|
|
@@ -203,6 +205,20 @@ const VideoDetails: React.FC<VideoDetailsProps> = (props) => {
|
|
|
203
205
|
>
|
|
204
206
|
<Stack space={4} flex={1} sizing="border">
|
|
205
207
|
<VideoPlayer asset={props.asset} autoPlay={props.asset.autoPlay || false} />
|
|
208
|
+
{tab === 'details' && (
|
|
209
|
+
<TextTracksManager
|
|
210
|
+
asset={props.asset}
|
|
211
|
+
iconOnly
|
|
212
|
+
collapseTracks
|
|
213
|
+
tracks={
|
|
214
|
+
displayInfo?.text_tracks ||
|
|
215
|
+
props.asset.data?.tracks?.filter(
|
|
216
|
+
(track): track is MuxTextTrack => track.type === 'text'
|
|
217
|
+
) ||
|
|
218
|
+
[]
|
|
219
|
+
}
|
|
220
|
+
/>
|
|
221
|
+
)}
|
|
206
222
|
</Stack>
|
|
207
223
|
<Stack space={4} flex={1} sizing="border">
|
|
208
224
|
<TabList space={2}>
|
|
@@ -279,13 +295,7 @@ const VideoDetails: React.FC<VideoDetailsProps> = (props) => {
|
|
|
279
295
|
size={2}
|
|
280
296
|
/>
|
|
281
297
|
<IconInfo text={`Mux ID: \n${displayInfo.id}`} icon={TagIcon} size={2} />
|
|
282
|
-
{displayInfo
|
|
283
|
-
<IconInfo
|
|
284
|
-
text={`Playback ID: ${displayInfo.playbackId}`}
|
|
285
|
-
icon={TagIcon}
|
|
286
|
-
size={2}
|
|
287
|
-
/>
|
|
288
|
-
)}
|
|
298
|
+
<PlaybackIds playback_ids={displayInfo.playback_ids} />
|
|
289
299
|
</Stack>
|
|
290
300
|
</Stack>
|
|
291
301
|
</TabPanel>
|
|
@@ -303,4 +313,30 @@ const VideoDetails: React.FC<VideoDetailsProps> = (props) => {
|
|
|
303
313
|
)
|
|
304
314
|
}
|
|
305
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
|
+
}
|
|
306
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,14 +2,19 @@ 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'
|
|
17
|
+
import CaptionsDialog from './CaptionsDialog'
|
|
13
18
|
import EditThumbnailDialog from './EditThumbnailDialog'
|
|
14
19
|
import {AudioIcon} from './icons/Audio'
|
|
15
20
|
|
|
@@ -32,32 +37,101 @@ export default function VideoPlayer({
|
|
|
32
37
|
|
|
33
38
|
const isAudio = assetIsAudio(asset)
|
|
34
39
|
const muxPlayer = useRef<MuxPlayerRefAttributes>(null)
|
|
40
|
+
const [error, setError] = useState<Error>()
|
|
35
41
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
thumbnail: thumbnailSrc,
|
|
39
|
-
error,
|
|
40
|
-
} = useMemo(() => {
|
|
42
|
+
/* Playback ID that will be used to play the video */
|
|
43
|
+
const playbackId = useMemo(() => {
|
|
41
44
|
try {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return {error: new TypeError('Asset has no playback ID')}
|
|
47
|
-
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
48
|
-
} catch (error) {
|
|
49
|
-
return {error}
|
|
45
|
+
return getPlaybackId(asset, ['public', 'signed', 'drm'])
|
|
46
|
+
} catch (e) {
|
|
47
|
+
setError(new TypeError('Asset has no playback ID'))
|
|
48
|
+
return undefined
|
|
50
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
|
+
)
|
|
51
77
|
}, [asset, client, thumbnailWidth])
|
|
52
78
|
|
|
53
79
|
const signedToken = useMemo(() => {
|
|
54
80
|
try {
|
|
55
|
-
const url = new URL(
|
|
81
|
+
const url = new URL(src!)
|
|
56
82
|
return url.searchParams.get('token')
|
|
57
83
|
} catch {
|
|
58
|
-
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
|
|
59
133
|
}
|
|
60
|
-
}, [
|
|
134
|
+
}, [signedToken, drmToken])
|
|
61
135
|
|
|
62
136
|
const [width, height] = (asset?.data?.aspect_ratio ?? '16:9').split(':').map(Number)
|
|
63
137
|
const targetAspectRatio =
|
|
@@ -70,6 +144,8 @@ export default function VideoPlayer({
|
|
|
70
144
|
: AUDIO_ASPECT_RATIO
|
|
71
145
|
}
|
|
72
146
|
|
|
147
|
+
/* We use Suspense here because `generateJwt` and related functions use suspend()
|
|
148
|
+
under the hood */
|
|
73
149
|
return (
|
|
74
150
|
<>
|
|
75
151
|
<Card
|
|
@@ -80,7 +156,7 @@ export default function VideoPlayer({
|
|
|
80
156
|
...(isAudio && {display: 'flex', alignItems: 'flex-end'}),
|
|
81
157
|
}}
|
|
82
158
|
>
|
|
83
|
-
{
|
|
159
|
+
{src && poster && (
|
|
84
160
|
<>
|
|
85
161
|
{isAudio && (
|
|
86
162
|
<AudioIcon
|
|
@@ -95,35 +171,33 @@ export default function VideoPlayer({
|
|
|
95
171
|
}}
|
|
96
172
|
/>
|
|
97
173
|
)}
|
|
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
|
-
{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>
|
|
127
201
|
</>
|
|
128
202
|
)}
|
|
129
203
|
{error ? (
|
|
@@ -149,6 +223,7 @@ export default function VideoPlayer({
|
|
|
149
223
|
{dialogState === 'edit-thumbnail' && (
|
|
150
224
|
<EditThumbnailDialog asset={asset} currentTime={muxPlayer?.current?.currentTime} />
|
|
151
225
|
)}
|
|
226
|
+
{dialogState === 'edit-captions' && <CaptionsDialog asset={asset} />}
|
|
152
227
|
</>
|
|
153
228
|
)
|
|
154
229
|
}
|