sanity-plugin-mux-input 3.0.4 → 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.
Files changed (130) hide show
  1. package/README.md +0 -2
  2. package/dist/index.d.ts +134 -193
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +2893 -4417
  5. package/dist/index.js.map +1 -1
  6. package/package.json +33 -43
  7. package/dist/index.cjs +0 -7270
  8. package/dist/index.cjs.map +0 -1
  9. package/dist/index.d.cts +0 -347
  10. package/sanity.json +0 -8
  11. package/src/_exports/index.ts +0 -73
  12. package/src/actions/assets.ts +0 -152
  13. package/src/actions/secrets.ts +0 -111
  14. package/src/actions/upload.ts +0 -310
  15. package/src/clients/upChunkObservable.ts +0 -54
  16. package/src/components/AddCaptionDialog.tsx +0 -440
  17. package/src/components/CaptionsDialog.tsx +0 -23
  18. package/src/components/ConfigureApi.styled.tsx +0 -19
  19. package/src/components/ConfigureApi.tsx +0 -296
  20. package/src/components/DraggableWatermark.tsx +0 -877
  21. package/src/components/EditCaptionDialog.tsx +0 -510
  22. package/src/components/EditThumbnailDialog.tsx +0 -122
  23. package/src/components/ErrorBoundaryCard.tsx +0 -96
  24. package/src/components/FileInputButton.tsx +0 -54
  25. package/src/components/FileInputMenuItem.styled.tsx +0 -36
  26. package/src/components/FileInputMenuItem.tsx +0 -85
  27. package/src/components/FormField.tsx +0 -38
  28. package/src/components/IconInfo.tsx +0 -22
  29. package/src/components/ImportVideosFromMux.tsx +0 -342
  30. package/src/components/Input.styled.tsx +0 -22
  31. package/src/components/Input.tsx +0 -78
  32. package/src/components/InputBrowser.tsx +0 -41
  33. package/src/components/InputError.tsx +0 -17
  34. package/src/components/MuxLogo.tsx +0 -42
  35. package/src/components/Onboard.tsx +0 -65
  36. package/src/components/PageSelector.tsx +0 -54
  37. package/src/components/Player.styled.tsx +0 -55
  38. package/src/components/Player.tsx +0 -117
  39. package/src/components/PlayerActionsMenu.tsx +0 -190
  40. package/src/components/ResyncMetadata.tsx +0 -280
  41. package/src/components/SelectAsset.tsx +0 -39
  42. package/src/components/SelectSortOptions.tsx +0 -45
  43. package/src/components/SpinnerBox.tsx +0 -16
  44. package/src/components/StudioTool.tsx +0 -24
  45. package/src/components/TextTracksEditor.tsx +0 -117
  46. package/src/components/TextTracksManager.tsx +0 -737
  47. package/src/components/UploadConfiguration.tsx +0 -694
  48. package/src/components/UploadPlaceholder.tsx +0 -88
  49. package/src/components/UploadProgress.tsx +0 -80
  50. package/src/components/Uploader.styled.tsx +0 -66
  51. package/src/components/Uploader.tsx +0 -498
  52. package/src/components/VideoDetails/DeleteDialog.tsx +0 -147
  53. package/src/components/VideoDetails/VideoDetails.tsx +0 -358
  54. package/src/components/VideoDetails/VideoReferences.tsx +0 -63
  55. package/src/components/VideoDetails/useVideoDetails.ts +0 -103
  56. package/src/components/VideoInBrowser.tsx +0 -245
  57. package/src/components/VideoMetadata.tsx +0 -45
  58. package/src/components/VideoPlayer.tsx +0 -235
  59. package/src/components/VideoThumbnail.tsx +0 -138
  60. package/src/components/VideosBrowser.tsx +0 -100
  61. package/src/components/documentPreview/DocumentPreview.tsx +0 -83
  62. package/src/components/documentPreview/DraftStatus.tsx +0 -34
  63. package/src/components/documentPreview/MissingSchemaType.tsx +0 -32
  64. package/src/components/documentPreview/PaneItemPreview.tsx +0 -74
  65. package/src/components/documentPreview/PublishedStatus.tsx +0 -35
  66. package/src/components/documentPreview/TimeAgo.tsx +0 -12
  67. package/src/components/documentPreview/paneItemTypes.ts +0 -7
  68. package/src/components/icons/Audio.tsx +0 -13
  69. package/src/components/icons/Resolution.tsx +0 -12
  70. package/src/components/icons/StopWatch.tsx +0 -20
  71. package/src/components/icons/ToolIcon.tsx +0 -19
  72. package/src/components/uploadConfiguration/PlaybackPolicy.tsx +0 -133
  73. package/src/components/uploadConfiguration/PlaybackPolicyOption.tsx +0 -76
  74. package/src/components/uploadConfiguration/PlaybackPolicyWarning.tsx +0 -29
  75. package/src/components/uploadConfiguration/ResolutionTierSelector.tsx +0 -72
  76. package/src/components/uploadConfiguration/StaticRenditionSelector.tsx +0 -180
  77. package/src/components/withFocusRing/helpers.ts +0 -24
  78. package/src/components/withFocusRing/index.ts +0 -1
  79. package/src/components/withFocusRing/withFocusRing.ts +0 -30
  80. package/src/context/DialogStateContext.tsx +0 -36
  81. package/src/context/DrmPlaybackWarningContext.tsx +0 -93
  82. package/src/hooks/useAccessControl.ts +0 -13
  83. package/src/hooks/useAssetDocumentValues.ts +0 -11
  84. package/src/hooks/useAssets.ts +0 -68
  85. package/src/hooks/useCancelUpload.ts +0 -22
  86. package/src/hooks/useClient.ts +0 -8
  87. package/src/hooks/useDialogState.ts +0 -11
  88. package/src/hooks/useDocReferences.ts +0 -21
  89. package/src/hooks/useFetchFileSize.ts +0 -54
  90. package/src/hooks/useImportMuxAssets.ts +0 -132
  91. package/src/hooks/useInView.ts +0 -42
  92. package/src/hooks/useMediaMetadata.ts +0 -103
  93. package/src/hooks/useMuxAssets.ts +0 -179
  94. package/src/hooks/useMuxPolling.ts +0 -49
  95. package/src/hooks/useResyncAsset.ts +0 -110
  96. package/src/hooks/useResyncMuxMetadata.ts +0 -176
  97. package/src/hooks/useSaveSecrets.ts +0 -78
  98. package/src/hooks/useSecretsDocumentValues.ts +0 -38
  99. package/src/hooks/useSecretsFormState.ts +0 -47
  100. package/src/plugin.tsx +0 -31
  101. package/src/sanity-ui.d.ts +0 -5
  102. package/src/schema.ts +0 -196
  103. package/src/util/addKeysToMuxData.ts +0 -30
  104. package/src/util/areSecretsSignable.ts +0 -5
  105. package/src/util/asserters.ts +0 -36
  106. package/src/util/assetTitlePlaceholder.ts +0 -31
  107. package/src/util/constants.ts +0 -15
  108. package/src/util/convertWatermarkToMux.ts +0 -160
  109. package/src/util/createSearchFilter.ts +0 -76
  110. package/src/util/createUrlParamsObject.ts +0 -29
  111. package/src/util/extractFiles.ts +0 -67
  112. package/src/util/formatBytes.ts +0 -32
  113. package/src/util/formatDriveShareLink.ts +0 -64
  114. package/src/util/formatSeconds.ts +0 -49
  115. package/src/util/generateJwt.ts +0 -57
  116. package/src/util/getAnimatedPosterSrc.ts +0 -26
  117. package/src/util/getPlaybackPolicy.ts +0 -69
  118. package/src/util/getPosterSrc.ts +0 -28
  119. package/src/util/getStoryboardSrc.ts +0 -27
  120. package/src/util/getVideoMetadata.ts +0 -23
  121. package/src/util/getVideoSrc.ts +0 -23
  122. package/src/util/isSigned.ts +0 -20
  123. package/src/util/parsers.ts +0 -5
  124. package/src/util/pluginVersion.ts +0 -1
  125. package/src/util/readSecrets.ts +0 -38
  126. package/src/util/roundPxString.ts +0 -16
  127. package/src/util/textTracks.ts +0 -222
  128. package/src/util/tryWithSuspend.ts +0 -22
  129. package/src/util/types.ts +0 -596
  130. 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 {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,235 +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
- setError(new TypeError('Asset has no playback ID'))
49
- return undefined
50
- }
51
- }, [asset])
52
-
53
- const muxPlaybackId = useMemo(() => {
54
- if (!playbackId) return undefined
55
- return getPlaybackPolicyById(asset, playbackId)
56
- }, [asset, playbackId])
57
-
58
- const src = useMemo(() => {
59
- if (!playbackId) return undefined
60
- if (!muxPlaybackId) return undefined
61
- return tryWithSuspend(
62
- () => getVideoSrc({muxPlaybackId, client}),
63
- (e: Error) => {
64
- setError(e)
65
- return undefined
66
- }
67
- )
68
- }, [muxPlaybackId, playbackId, client])
69
-
70
- const poster = useMemo(() => {
71
- return tryWithSuspend(
72
- () => getPosterSrc({asset, client, width: thumbnailWidth}),
73
- (e: Error) => {
74
- setError(e)
75
- return undefined
76
- }
77
- )
78
- }, [asset, client, thumbnailWidth])
79
-
80
- const signedToken = useMemo(() => {
81
- try {
82
- const url = new URL(src!)
83
- return url.searchParams.get('token')
84
- } catch {
85
- return undefined
86
- }
87
- }, [src])
88
- const drmToken = useMemo(() => {
89
- if (!playbackId) return undefined
90
- if (muxPlaybackId?.policy !== 'drm') return undefined
91
-
92
- return tryWithSuspend(
93
- () => generateJwt(client, playbackId, 'd'),
94
- (e: Error) => {
95
- setError(e)
96
- return undefined
97
- }
98
- )
99
- }, [client, muxPlaybackId?.policy, playbackId])
100
- const tokens:
101
- | Partial<{
102
- playback?: string
103
- thumbnail?: string
104
- storyboard?: string
105
- drm?: string
106
- }>
107
- | undefined = useMemo(() => {
108
- try {
109
- const partialTokens: {
110
- playback?: string
111
- thumbnail?: string
112
- storyboard?: string
113
- drm?: string
114
- } = {
115
- playback: undefined,
116
- thumbnail: undefined,
117
- storyboard: undefined,
118
- drm: undefined,
119
- }
120
-
121
- if (signedToken) {
122
- partialTokens.playback = signedToken
123
- partialTokens.thumbnail = signedToken
124
- partialTokens.storyboard = signedToken
125
- }
126
-
127
- if (drmToken) {
128
- partialTokens.drm = drmToken
129
- }
130
-
131
- return {...partialTokens}
132
- } catch {
133
- return undefined
134
- }
135
- }, [signedToken, drmToken])
136
-
137
- const [width, height] = (asset?.data?.aspect_ratio ?? '16:9').split(':').map(Number)
138
- const targetAspectRatio =
139
- props.forceAspectRatio || (Number.isNaN(width) ? 16 / 9 : width / height)
140
- let aspectRatio = Math.max(MIN_ASPECT_RATIO, targetAspectRatio)
141
- if (isAudio) {
142
- aspectRatio = props.forceAspectRatio
143
- ? // Make it wider when forcing aspect ratio to balance with videos' rendering height (audio players overflow a bit)
144
- props.forceAspectRatio * 1.2
145
- : AUDIO_ASPECT_RATIO
146
- }
147
-
148
- /* We use Suspense here because `generateJwt` and related functions use suspend()
149
- under the hood */
150
- return (
151
- <>
152
- <Card
153
- ref={playerContainerRef}
154
- tone="transparent"
155
- style={{
156
- aspectRatio: aspectRatio,
157
- position: 'relative',
158
- ...(isAudio && {display: 'flex', alignItems: 'flex-end'}),
159
- }}
160
- >
161
- {src && poster && (
162
- <>
163
- {isAudio && (
164
- <AudioIcon
165
- style={{
166
- padding: '0.5em',
167
- width: '2.2em',
168
- height: '2.2em',
169
- position: 'absolute',
170
- top: 0,
171
- left: 0,
172
- zIndex: 1,
173
- }}
174
- />
175
- )}
176
- <Suspense fallback={null}>
177
- <MuxPlayer
178
- poster={isAudio ? undefined : poster}
179
- ref={muxPlayer}
180
- {...props}
181
- playsInline
182
- playbackId={playbackId}
183
- tokens={tokens}
184
- preload="metadata"
185
- crossOrigin="anonymous"
186
- metadata={{
187
- player_name: 'Sanity Admin Dashboard',
188
- player_version: process.env.PKG_VERSION,
189
- page_type: 'Preview Player',
190
- }}
191
- audio={isAudio}
192
- _hlsConfig={hlsConfig}
193
- style={{
194
- ...(!isAudio && {height: '100%'}),
195
- width: '100%',
196
- display: 'block',
197
- objectFit: 'contain',
198
- ...(isAudio && {alignSelf: 'end'}),
199
- }}
200
- />
201
- {children}
202
- </Suspense>
203
- </>
204
- )}
205
- {error ? (
206
- <div
207
- style={{
208
- position: 'absolute',
209
- top: '50%',
210
- left: '50%',
211
- transform: 'translate(-50%, -50%)',
212
- }}
213
- >
214
- <Text muted>
215
- <ErrorOutlineIcon style={{marginRight: '0.15em'}} />
216
- {typeof error === 'object' && 'message' in error && typeof error.message === 'string'
217
- ? error.message
218
- : 'Error loading video'}
219
- </Text>
220
- </div>
221
- ) : null}
222
- {children}
223
- </Card>
224
-
225
- {dialogState === 'edit-thumbnail' && (
226
- <EditThumbnailDialog asset={asset} currentTime={muxPlayer?.current?.currentTime} />
227
- )}
228
- {dialogState === 'edit-captions' && <CaptionsDialog asset={asset} />}
229
- </>
230
- )
231
- }
232
-
233
- export function assetIsAudio(asset: VideoAssetDocument) {
234
- return asset.data?.max_stored_resolution === 'Audio only'
235
- }
@@ -1,138 +0,0 @@
1
- import {ErrorOutlineIcon} from '@sanity/icons'
2
- import {Box, Card, 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 {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
- export default function VideoThumbnail({
32
- asset,
33
- width,
34
- staticImage = false,
35
- }: {
36
- asset: AssetThumbnailOptions['asset']
37
- width?: number
38
- staticImage?: boolean
39
- }) {
40
- const posterWidth = width || 250
41
- const client = useClient()
42
- const ref = useRef<HTMLDivElement | null>(null)
43
- const inView = useInView(ref)
44
-
45
- const [status, setStatus] = useState<ImageStatus>('loading')
46
- const [error, setError] = useState<string | null>(null)
47
-
48
- const thumbnailSrc = useMemo(() => {
49
- return tryWithSuspend(
50
- () => {
51
- let thumbnail: MuxAnimatedThumbnailUrl | MuxThumbnailUrl | undefined
52
-
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])
63
-
64
- function handleLoad() {
65
- setStatus('loaded')
66
- }
67
-
68
- function handleError(err?: string) {
69
- setStatus('error')
70
- if (err) {
71
- setError(err)
72
- } else {
73
- setError('Failed loading thumbnail')
74
- }
75
- }
76
-
77
- return (
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>
137
- )
138
- }