sanity-plugin-mux-input 2.13.0 → 2.14.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.d.mts +22 -1
- package/dist/index.d.ts +22 -1
- package/dist/index.js +1393 -98
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1394 -99
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/actions/assets.ts +75 -0
- package/src/components/AddCaptionDialog.tsx +421 -0
- package/src/components/CaptionsDialog.tsx +23 -0
- package/src/components/EditCaptionDialog.tsx +508 -0
- package/src/components/Onboard.tsx +2 -2
- package/src/components/PageSelector.tsx +57 -0
- package/src/components/PlayerActionsMenu.tsx +13 -5
- package/src/components/TextTracksManager.tsx +781 -0
- package/src/components/Uploader.styled.tsx +8 -15
- package/src/components/Uploader.tsx +7 -0
- package/src/components/VideoDetails/VideoDetails.tsx +16 -0
- package/src/components/VideoPlayer.tsx +2 -0
- package/src/components/VideosBrowser.tsx +9 -1
- package/src/hooks/useAccessControl.ts +1 -0
- package/src/hooks/useDialogState.ts +1 -1
- package/src/util/getVideoMetadata.ts +3 -1
- package/src/util/textTracks.ts +219 -0
- package/src/util/types.ts +12 -4
|
@@ -5,9 +5,6 @@ import {styled} from 'styled-components'
|
|
|
5
5
|
|
|
6
6
|
import {withFocusRing} from './withFocusRing'
|
|
7
7
|
|
|
8
|
-
const ctrlKey = 17
|
|
9
|
-
const cmdKey = 91
|
|
10
|
-
|
|
11
8
|
const UploadCardWithFocusRing = withFocusRing(Card)
|
|
12
9
|
|
|
13
10
|
interface UploadCardProps {
|
|
@@ -21,22 +18,19 @@ interface UploadCardProps {
|
|
|
21
18
|
}
|
|
22
19
|
export const UploadCard = forwardRef<HTMLDivElement, UploadCardProps>(
|
|
23
20
|
({children, tone, onPaste, onDrop, onDragEnter, onDragLeave, onDragOver}, forwardedRef) => {
|
|
24
|
-
const ctrlDown = useRef(false)
|
|
25
21
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
26
22
|
const handleKeyDown = useCallback<React.KeyboardEventHandler<HTMLDivElement>>((event) => {
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
const target = event.target as HTMLElement
|
|
24
|
+
|
|
25
|
+
// Don't steal focus when pasting into the VTT input
|
|
26
|
+
if (target.closest('#vtt-url')) {
|
|
27
|
+
return
|
|
29
28
|
}
|
|
30
|
-
|
|
31
|
-
if (
|
|
29
|
+
|
|
30
|
+
if ((event.ctrlKey || event.metaKey) && event.key === 'v') {
|
|
32
31
|
inputRef.current!.focus()
|
|
33
32
|
}
|
|
34
33
|
}, [])
|
|
35
|
-
const handleKeyUp = useCallback<React.KeyboardEventHandler<HTMLDivElement>>((event) => {
|
|
36
|
-
if (event.keyCode == ctrlKey || event.keyCode == cmdKey) {
|
|
37
|
-
ctrlDown.current = false
|
|
38
|
-
}
|
|
39
|
-
}, [])
|
|
40
34
|
|
|
41
35
|
return (
|
|
42
36
|
<UploadCardWithFocusRing
|
|
@@ -47,14 +41,13 @@ export const UploadCard = forwardRef<HTMLDivElement, UploadCardProps>(
|
|
|
47
41
|
shadow={0}
|
|
48
42
|
tabIndex={0}
|
|
49
43
|
onKeyDown={handleKeyDown}
|
|
50
|
-
onKeyUp={handleKeyUp}
|
|
51
44
|
onPaste={onPaste}
|
|
52
45
|
onDrop={onDrop}
|
|
53
46
|
onDragEnter={onDragEnter}
|
|
54
47
|
onDragLeave={onDragLeave}
|
|
55
48
|
onDragOver={onDragOver}
|
|
56
49
|
>
|
|
57
|
-
<HiddenInput ref={inputRef}
|
|
50
|
+
<HiddenInput ref={inputRef} />
|
|
58
51
|
{children}
|
|
59
52
|
</UploadCardWithFocusRing>
|
|
60
53
|
)
|
|
@@ -297,6 +297,13 @@ export default function Uploader(props: Props) {
|
|
|
297
297
|
|
|
298
298
|
// Stages and validates an upload from pasting an asset URL
|
|
299
299
|
const handlePaste: React.ClipboardEventHandler<HTMLInputElement> = (event) => {
|
|
300
|
+
const target = event.target as HTMLElement
|
|
301
|
+
|
|
302
|
+
// Ignore paste coming from the VTT URL input
|
|
303
|
+
if (target.closest('#vtt-url')) {
|
|
304
|
+
return
|
|
305
|
+
}
|
|
306
|
+
|
|
300
307
|
event.preventDefault()
|
|
301
308
|
event.stopPropagation()
|
|
302
309
|
const clipboardData =
|
|
@@ -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 type {MuxTextTrack} 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}>
|
|
@@ -10,6 +10,7 @@ import {AUDIO_ASPECT_RATIO, MIN_ASPECT_RATIO} from '../util/constants'
|
|
|
10
10
|
import {getPosterSrc} from '../util/getPosterSrc'
|
|
11
11
|
import {getVideoSrc} from '../util/getVideoSrc'
|
|
12
12
|
import type {VideoAssetDocument} from '../util/types'
|
|
13
|
+
import CaptionsDialog from './CaptionsDialog'
|
|
13
14
|
import EditThumbnailDialog from './EditThumbnailDialog'
|
|
14
15
|
import {AudioIcon} from './icons/Audio'
|
|
15
16
|
|
|
@@ -149,6 +150,7 @@ export default function VideoPlayer({
|
|
|
149
150
|
{dialogState === 'edit-thumbnail' && (
|
|
150
151
|
<EditThumbnailDialog asset={asset} currentTime={muxPlayer?.current?.currentTime} />
|
|
151
152
|
)}
|
|
153
|
+
{dialogState === 'edit-captions' && <CaptionsDialog asset={asset} />}
|
|
152
154
|
</>
|
|
153
155
|
)
|
|
154
156
|
}
|
|
@@ -6,6 +6,7 @@ import useAssets from '../hooks/useAssets'
|
|
|
6
6
|
import type {VideoAssetDocument} from '../util/types'
|
|
7
7
|
import ConfigureApi from './ConfigureApi'
|
|
8
8
|
import ImportVideosFromMux from './ImportVideosFromMux'
|
|
9
|
+
import PageSelector from './PageSelector'
|
|
9
10
|
import ResyncMetadata from './ResyncMetadata'
|
|
10
11
|
import {SelectSortOptions} from './SelectSortOptions'
|
|
11
12
|
import SpinnerBox from './SpinnerBox'
|
|
@@ -19,12 +20,18 @@ export interface VideosBrowserProps {
|
|
|
19
20
|
|
|
20
21
|
export default function VideosBrowser({onSelect}: VideosBrowserProps) {
|
|
21
22
|
const {assets, isLoading, searchQuery, setSearchQuery, setSort, sort} = useAssets()
|
|
23
|
+
const [page, setPage] = useState<number>(0)
|
|
24
|
+
const pageLimit = 20
|
|
25
|
+
const pageTotal = Math.floor(assets.length / pageLimit) + 1
|
|
22
26
|
const [editedAsset, setEditedAsset] = useState<VideoDetailsProps['asset'] | null>(null)
|
|
23
27
|
const freshEditedAsset = useMemo(
|
|
24
28
|
() => assets.find((a) => a._id === editedAsset?._id) || editedAsset,
|
|
25
29
|
[editedAsset, assets]
|
|
26
30
|
)
|
|
27
31
|
|
|
32
|
+
const pageStart = page * pageLimit
|
|
33
|
+
const pageEnd = pageStart + pageLimit
|
|
34
|
+
|
|
28
35
|
const placement = onSelect ? 'input' : 'tool'
|
|
29
36
|
return (
|
|
30
37
|
<>
|
|
@@ -40,6 +47,7 @@ export default function VideosBrowser({onSelect}: VideosBrowserProps) {
|
|
|
40
47
|
placeholder="Search videos"
|
|
41
48
|
/>
|
|
42
49
|
<SelectSortOptions setSort={setSort} sort={sort} />
|
|
50
|
+
<PageSelector page={page} setPage={setPage} total={pageTotal} limit={pageLimit} />
|
|
43
51
|
</Flex>
|
|
44
52
|
{placement === 'tool' && (
|
|
45
53
|
<Inline space={2}>
|
|
@@ -62,7 +70,7 @@ export default function VideosBrowser({onSelect}: VideosBrowserProps) {
|
|
|
62
70
|
gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))',
|
|
63
71
|
}}
|
|
64
72
|
>
|
|
65
|
-
{assets.map((asset) => (
|
|
73
|
+
{assets.slice(pageStart, pageEnd).map((asset) => (
|
|
66
74
|
<VideoInBrowser
|
|
67
75
|
key={asset._id}
|
|
68
76
|
asset={asset}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import {useState} from 'react'
|
|
4
4
|
|
|
5
|
-
export type DialogState = 'secrets' | 'select-video' | 'edit-thumbnail' | false
|
|
5
|
+
export type DialogState = 'secrets' | 'select-video' | 'edit-thumbnail' | 'edit-captions' | false
|
|
6
6
|
|
|
7
7
|
export function useDialogState() {
|
|
8
8
|
return useState<DialogState>(false)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {formatSeconds} from './formatSeconds'
|
|
2
|
-
import {VideoAssetDocument} from './types'
|
|
2
|
+
import type {MuxTextTrack, VideoAssetDocument} from './types'
|
|
3
3
|
|
|
4
4
|
export default function getVideoMetadata(doc: VideoAssetDocument) {
|
|
5
5
|
const id = doc.assetId || doc._id || ''
|
|
@@ -16,5 +16,7 @@ export default function getVideoMetadata(doc: VideoAssetDocument) {
|
|
|
16
16
|
aspect_ratio: doc.data?.aspect_ratio,
|
|
17
17
|
max_stored_resolution: doc.data?.max_stored_resolution,
|
|
18
18
|
max_stored_frame_rate: doc.data?.max_stored_frame_rate,
|
|
19
|
+
text_tracks:
|
|
20
|
+
doc.data?.tracks?.filter((track): track is MuxTextTrack => track.type === 'text') || [],
|
|
19
21
|
}
|
|
20
22
|
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import type {SanityClient} from '@sanity/client'
|
|
2
|
+
|
|
3
|
+
import {getAsset} from '../actions/assets'
|
|
4
|
+
import {generateJwt} from './generateJwt'
|
|
5
|
+
import {getPlaybackId} from './getPlaybackId'
|
|
6
|
+
import {getPlaybackPolicy} from './getPlaybackPolicy'
|
|
7
|
+
import type {MuxTextTrack, VideoAssetDocument} from './types'
|
|
8
|
+
|
|
9
|
+
export function extractErrorMessage(
|
|
10
|
+
error: unknown,
|
|
11
|
+
defaultMessage = 'Failed to process request'
|
|
12
|
+
): string {
|
|
13
|
+
let message = ''
|
|
14
|
+
|
|
15
|
+
if (error && typeof error === 'object') {
|
|
16
|
+
const err = error as {response?: {body?: {message?: string}}; message?: string}
|
|
17
|
+
message = err.response?.body?.message || err.message || ''
|
|
18
|
+
} else if (typeof error === 'string') {
|
|
19
|
+
message = error
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!message) {
|
|
23
|
+
return defaultMessage
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const match = message.match(/\(([^)]+)\)/)
|
|
27
|
+
if (match && match[1]) {
|
|
28
|
+
return match[1]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (message.includes('responded with')) {
|
|
32
|
+
const parts = message.split('(')
|
|
33
|
+
if (parts.length > 1) {
|
|
34
|
+
return parts[parts.length - 1].replace(')', '').trim()
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return message
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface PollTrackStatusOptions {
|
|
42
|
+
client: SanityClient
|
|
43
|
+
assetId: string
|
|
44
|
+
trackName: string
|
|
45
|
+
trackLanguageCode: string
|
|
46
|
+
maxAttempts?: number
|
|
47
|
+
onTrackFound?: (track: MuxTextTrack) => void
|
|
48
|
+
onTrackErrored?: (track: MuxTextTrack) => void
|
|
49
|
+
onTrackReady?: (track: MuxTextTrack) => void
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface PollTrackStatusResult {
|
|
53
|
+
track: MuxTextTrack | undefined
|
|
54
|
+
found: boolean
|
|
55
|
+
status: 'ready' | 'preparing' | 'errored' | 'not-found'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Polls Mux API to find and track the status of a newly added text track.
|
|
60
|
+
* The track may be in "preparing" state initially, then become "ready" or "errored".
|
|
61
|
+
*
|
|
62
|
+
* @param options - Configuration options for polling
|
|
63
|
+
* @returns Promise resolving to the poll result
|
|
64
|
+
*/
|
|
65
|
+
export async function pollTrackStatus(
|
|
66
|
+
options: PollTrackStatusOptions
|
|
67
|
+
): Promise<PollTrackStatusResult> {
|
|
68
|
+
const {
|
|
69
|
+
client,
|
|
70
|
+
assetId,
|
|
71
|
+
trackName,
|
|
72
|
+
trackLanguageCode,
|
|
73
|
+
maxAttempts = 10,
|
|
74
|
+
onTrackFound,
|
|
75
|
+
onTrackErrored,
|
|
76
|
+
onTrackReady,
|
|
77
|
+
} = options
|
|
78
|
+
|
|
79
|
+
const trimmedName = trackName.trim()
|
|
80
|
+
const trimmedLanguageCode = trackLanguageCode.trim()
|
|
81
|
+
let newTrack: MuxTextTrack | undefined
|
|
82
|
+
let attempts = 0
|
|
83
|
+
let trackFound = false
|
|
84
|
+
|
|
85
|
+
const findTrack = (textTracks: MuxTextTrack[]): MuxTextTrack | undefined => {
|
|
86
|
+
let foundTrack = textTracks.find(
|
|
87
|
+
(track) => track.name === trimmedName && track.language_code === trimmedLanguageCode
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if (!foundTrack) {
|
|
91
|
+
foundTrack = textTracks.find((track) => track.language_code === trimmedLanguageCode)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!foundTrack && textTracks.length > 0) {
|
|
95
|
+
foundTrack = textTracks[textTracks.length - 1]
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return foundTrack
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
while (attempts < maxAttempts) {
|
|
102
|
+
try {
|
|
103
|
+
if (attempts > 0) {
|
|
104
|
+
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const assetData = await getAsset(client, assetId)
|
|
108
|
+
const textTracks =
|
|
109
|
+
assetData.data.tracks?.filter((track): track is MuxTextTrack => track.type === 'text') || []
|
|
110
|
+
|
|
111
|
+
const foundTrack = findTrack(textTracks)
|
|
112
|
+
|
|
113
|
+
if (!foundTrack) {
|
|
114
|
+
attempts++
|
|
115
|
+
continue
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
trackFound = true
|
|
119
|
+
newTrack = foundTrack
|
|
120
|
+
|
|
121
|
+
if (onTrackFound) {
|
|
122
|
+
onTrackFound(foundTrack)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (foundTrack.status === 'ready') {
|
|
126
|
+
if (onTrackReady) {
|
|
127
|
+
onTrackReady(foundTrack)
|
|
128
|
+
}
|
|
129
|
+
break
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (foundTrack.status === 'errored') {
|
|
133
|
+
if (onTrackErrored) {
|
|
134
|
+
onTrackErrored(foundTrack)
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
track: foundTrack,
|
|
138
|
+
found: true,
|
|
139
|
+
status: 'errored',
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error('Failed to fetch updated asset:', error)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
attempts++
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!newTrack || !trackFound) {
|
|
150
|
+
return {
|
|
151
|
+
track: undefined,
|
|
152
|
+
found: false,
|
|
153
|
+
status: 'not-found',
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (newTrack.status === 'preparing') {
|
|
158
|
+
return {
|
|
159
|
+
track: newTrack,
|
|
160
|
+
found: true,
|
|
161
|
+
status: 'preparing',
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
track: newTrack,
|
|
167
|
+
found: true,
|
|
168
|
+
status: 'ready',
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export async function downloadVttFile(
|
|
173
|
+
client: SanityClient,
|
|
174
|
+
asset: VideoAssetDocument,
|
|
175
|
+
track: MuxTextTrack
|
|
176
|
+
): Promise<void> {
|
|
177
|
+
if (!track.id) {
|
|
178
|
+
throw new Error('Track ID is missing')
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (track.status !== 'ready') {
|
|
182
|
+
throw new Error(`Track is not ready yet. Status: ${track.status}`)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!asset.assetId) {
|
|
186
|
+
throw new Error('Asset ID is required')
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const playbackId = getPlaybackId(asset)
|
|
190
|
+
if (!playbackId) {
|
|
191
|
+
throw new Error('Playback ID is required')
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const playbackPolicy = getPlaybackPolicy(asset)
|
|
195
|
+
|
|
196
|
+
let downloadUrl = `https://stream.mux.com/${playbackId}/text/${track.id}.vtt`
|
|
197
|
+
|
|
198
|
+
if (playbackPolicy === 'signed') {
|
|
199
|
+
const token = generateJwt(client, playbackId, 'v')
|
|
200
|
+
downloadUrl += `?token=${token}`
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const response = await fetch(downloadUrl)
|
|
204
|
+
if (!response.ok) {
|
|
205
|
+
throw new Error(`Failed to download file: ${response.statusText}`)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const blob = await response.blob()
|
|
209
|
+
const blobUrl = URL.createObjectURL(blob)
|
|
210
|
+
|
|
211
|
+
const link = document.createElement('a')
|
|
212
|
+
link.href = blobUrl
|
|
213
|
+
link.download = `${asset.filename || 'captions'}-${track.language_code || 'en'}.vtt`
|
|
214
|
+
document.body.appendChild(link)
|
|
215
|
+
link.click()
|
|
216
|
+
document.body.removeChild(link)
|
|
217
|
+
|
|
218
|
+
URL.revokeObjectURL(blobUrl)
|
|
219
|
+
}
|
package/src/util/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import type MuxPlayerElement from '@mux/mux-player'
|
|
1
2
|
import type {ObjectInputProps, PreviewLayoutKey, PreviewProps, SchemaType} from 'sanity'
|
|
2
3
|
import type {PartialDeep} from 'type-fest'
|
|
3
|
-
import type MuxPlayerElement from '@mux/mux-player'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Standard static rendition options available for plugin configuration defaults
|
|
@@ -303,7 +303,6 @@ export interface MuxNewAssetSettings
|
|
|
303
303
|
name?: string
|
|
304
304
|
/** Indicates the track provides Subtitles for the Deaf or Hard-of-hearing (SDH). */
|
|
305
305
|
closed_captions?: boolean
|
|
306
|
-
/// @TODO Huhh?>?? Below
|
|
307
306
|
/** This optional parameter should be used tracks with type of text and text_type set to subtitles. */
|
|
308
307
|
passthrough?: string
|
|
309
308
|
}[]
|
|
@@ -401,7 +400,12 @@ export interface MuxTextTrack {
|
|
|
401
400
|
id: string
|
|
402
401
|
text_type?: 'subtitles'
|
|
403
402
|
// https://docs.mux.com/api-reference/video#operation/list-assets:~:text=text%20type%20tracks.-,tracks%5B%5D.,text_source,-string
|
|
404
|
-
text_source?:
|
|
403
|
+
text_source?:
|
|
404
|
+
| 'uploaded'
|
|
405
|
+
| 'embedded'
|
|
406
|
+
| 'generated_live'
|
|
407
|
+
| 'generated_live_final'
|
|
408
|
+
| 'generated_vod'
|
|
405
409
|
// BCP 47 language code
|
|
406
410
|
language_code?: 'en' | 'en-US' | string
|
|
407
411
|
// The name of the track containing a human-readable description. The hls manifest will associate a subtitle text track with this value
|
|
@@ -410,8 +414,12 @@ export interface MuxTextTrack {
|
|
|
410
414
|
// Max 255 characters
|
|
411
415
|
passthrough?: string
|
|
412
416
|
status: 'preparing' | 'ready' | 'errored'
|
|
417
|
+
error?: {
|
|
418
|
+
type: string
|
|
419
|
+
messages?: string[]
|
|
420
|
+
}
|
|
413
421
|
}
|
|
414
|
-
export type MuxTrack = MuxVideoTrack | MuxAudioTrack
|
|
422
|
+
export type MuxTrack = MuxVideoTrack | MuxAudioTrack | MuxTextTrack
|
|
415
423
|
// Typings lifted from https://docs.mux.com/api-reference/video#tag/assets
|
|
416
424
|
export interface MuxAsset {
|
|
417
425
|
id: string
|