sanity-plugin-mux-input 2.3.4 → 2.3.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sanity-plugin-mux-input",
3
- "version": "2.3.4",
3
+ "version": "2.3.5",
4
4
  "description": "An input component that integrates Sanity Studio with Mux video encoding/hosting service.",
5
5
  "keywords": [
6
6
  "sanity",
@@ -22,45 +22,42 @@
22
22
  "license": "MIT",
23
23
  "author": "Sanity.io <hello@sanity.io>",
24
24
  "sideEffects": false,
25
- "type": "module",
25
+ "type": "commonjs",
26
26
  "exports": {
27
27
  ".": {
28
28
  "source": "./src/_exports/index.ts",
29
- "import": "./lib/index.js",
30
- "require": "./lib/index.cjs",
31
- "default": "./lib/index.js"
29
+ "import": "./dist/index.mjs",
30
+ "default": "./dist/index.js"
32
31
  },
33
32
  "./package.json": "./package.json"
34
33
  },
35
- "main": "./lib/index.cjs",
36
- "module": "./lib/index.esm.js",
37
- "source": "./src/_exports/index.ts",
38
- "types": "./lib/index.d.ts",
34
+ "main": "./dist/index.js",
35
+ "types": "./dist/index.d.ts",
39
36
  "files": [
40
37
  "src",
41
- "lib",
38
+ "dist",
42
39
  "sanity.json",
43
40
  "v2-incompatible.js"
44
41
  ],
45
42
  "scripts": {
46
- "build": "run-s clean && pkg-utils build --strict && pkg-utils --strict",
43
+ "build": "plugin-kit verify-package --silent && pkg-utils build --strict --check --clean",
47
44
  "clean": "rimraf lib",
48
- "dev": "plugin-kit link-watch",
45
+ "dev": "plugin-kit link-watch --strict",
49
46
  "format": "prettier --write --cache --ignore-unknown .",
50
47
  "link-watch": "plugin-kit link-watch",
51
48
  "lint": "eslint .",
52
49
  "prepare": "husky install || true",
53
- "prepublishOnly": "run-s build",
50
+ "prepublishOnly": "npm run build",
54
51
  "test": "npm run lint && npm run type-check && npm run build",
55
52
  "type-check": "tsc --noEmit",
56
53
  "watch": "pkg-utils watch --strict"
57
54
  },
58
55
  "dependencies": {
59
- "@mux/mux-player-react": "^2.4.1",
60
- "@mux/upchunk": "^3.3.2",
61
- "@sanity/icons": "^2.11.7",
56
+ "@mux/mux-player-react": "^2.6.0",
57
+ "@mux/upchunk": "^3.4.0",
58
+ "@sanity/icons": "^2.11.8",
62
59
  "@sanity/incompatible-plugin": "^1.0.4",
63
- "@sanity/ui": "^2.1.0",
60
+ "@sanity/ui": "^2.1.11",
64
61
  "@sanity/uuid": "^3.0.2",
65
62
  "iso-639-1": "^3.1.2",
66
63
  "jsonwebtoken-esm": "^1.0.5",
@@ -70,57 +67,64 @@
70
67
  "scroll-into-view-if-needed": "^3.1.0",
71
68
  "suspend-react": "^0.1.3",
72
69
  "swr": "^2.2.5",
73
- "type-fest": "^4.10.2"
70
+ "type-fest": "^4.18.2",
71
+ "use-error-boundary": "^2.0.6"
74
72
  },
75
73
  "devDependencies": {
76
- "@commitlint/cli": "^19.2.1",
77
- "@commitlint/config-conventional": "^19.1.0",
78
- "@sanity/client": "^6.15.11",
79
- "@sanity/pkg-utils": "^6.0.1",
80
- "@sanity/plugin-kit": "^3.1.10",
74
+ "@commitlint/cli": "^19.3.0",
75
+ "@commitlint/config-conventional": "^19.2.2",
76
+ "@sanity/client": "^6.18.2",
77
+ "@sanity/pkg-utils": "^6.8.16",
78
+ "@sanity/plugin-kit": "4.0.12",
81
79
  "@sanity/semantic-release-preset": "^4.1.7",
82
- "@sanity/vision": "^3.36.4",
83
- "@types/lodash": "^4.17.0",
84
- "@types/react": "^18.2.74",
85
- "@types/react-is": "^18.2.4",
86
- "@typescript-eslint/eslint-plugin": "^7.6.0",
87
- "@typescript-eslint/parser": "^7.6.0",
80
+ "@sanity/vision": "^3.42.1",
81
+ "@types/lodash": "^4.17.4",
82
+ "@types/react": "^18.3.2",
83
+ "@types/react-is": "^18.3.0",
84
+ "@types/styled-components": "^5.1.34",
85
+ "@typescript-eslint/eslint-plugin": "^7.0.2",
86
+ "@typescript-eslint/parser": "^7.0.2",
88
87
  "cz-conventional-changelog": "^3.3.0",
89
88
  "eslint": "^8.57.0",
90
89
  "eslint-config-prettier": "^9.1.0",
90
+ "eslint-config-react-app": "^7.0.1",
91
91
  "eslint-config-sanity": "^7.1.2",
92
92
  "eslint-plugin-import": "^2.29.1",
93
93
  "eslint-plugin-prettier": "^5.1.3",
94
- "eslint-plugin-react-hooks": "^4.6.0",
95
- "eslint-plugin-simple-import-sort": "^12.0.0",
94
+ "eslint-plugin-react-hooks": "^4.6.2",
95
+ "eslint-plugin-simple-import-sort": "^12.1.0",
96
96
  "husky": "^9.0.11",
97
97
  "lint-staged": "^15.2.2",
98
98
  "npm-run-all2": "^5.0.0",
99
99
  "prettier": "^3.2.5",
100
- "prettier-plugin-packagejson": "^2.4.14",
101
- "react": "^18.2.0",
102
- "react-dom": "^18.2.0",
103
- "react-is": "^18.2.0",
104
- "rimraf": "^5.0.5",
105
- "sanity": "^3.36.4",
106
- "semantic-release": "^23.0.7",
107
- "styled-components": "^6.1.8",
108
- "typescript": "5.4.2",
109
- "use-error-boundary": "^2.0.6",
100
+ "prettier-plugin-packagejson": "^2.5.0",
101
+ "react": "^18.3.1",
102
+ "react-dom": "^18.3.1",
103
+ "react-is": "^18.3.1",
104
+ "rimraf": "^5.0.7",
105
+ "sanity": "^3.42.1",
106
+ "styled-components": "^6.1.11",
107
+ "typescript": "^5.4.5",
110
108
  "yalc": "1.0.0-pre.53"
111
109
  },
112
110
  "peerDependencies": {
113
111
  "react": "^18",
114
112
  "react-is": "^18",
115
- "sanity": "^3",
116
- "styled-components": "^6"
113
+ "sanity": "^3.42.0",
114
+ "styled-components": "^5 || ^6"
117
115
  },
118
116
  "engines": {
119
- "node": ">=14"
117
+ "node": ">=18"
120
118
  },
121
119
  "publishConfig": {
122
120
  "access": "public",
123
121
  "provenance": true
124
122
  },
125
- "sanityExchangeUrl": "https://www.sanity.io/plugins/sanity-plugin-mux-input"
123
+ "sanityExchangeUrl": "https://www.sanity.io/plugins/sanity-plugin-mux-input",
124
+ "browserslist": "extends @sanity/browserslist-config",
125
+ "sanityPlugin": {
126
+ "verifyPackage": {
127
+ "srcIndex": false
128
+ }
129
+ }
126
130
  }
@@ -1,7 +1,8 @@
1
1
  import {definePlugin} from 'sanity'
2
+
2
3
  import createStudioTool, {DEFAULT_TOOL_CONFIG} from '../components/StudioTool'
3
4
  import {muxVideoCustomRendering} from '../plugin'
4
- import {muxVideo, muxVideoAsset} from '../schema'
5
+ import {muxVideoSchema, schemaTypes} from '../schema'
5
6
  import type {PluginConfig} from '../util/types'
6
7
  export type {VideoAssetDocument} from '../util/types'
7
8
 
@@ -20,9 +21,9 @@ export const muxInput = definePlugin<Partial<PluginConfig> | void>((userConfig)
20
21
  name: 'mux-input',
21
22
  schema: {
22
23
  types: [
23
- muxVideoAsset,
24
+ ...schemaTypes,
24
25
  {
25
- ...muxVideo,
26
+ ...muxVideoSchema,
26
27
  ...muxVideoCustomRendering(config),
27
28
  },
28
29
  ],
@@ -1,12 +1,11 @@
1
- import {AddIcon, DocumentTextIcon, ResetIcon, TranslateIcon, TrashIcon} from '@sanity/icons'
2
- import {Autocomplete, Button, Card, Code, Flex, Radio, Stack, Text} from '@sanity/ui'
1
+ import {TranslateIcon} from '@sanity/icons'
2
+ import {Autocomplete, Box, Card, Checkbox, Flex, Stack, Text} from '@sanity/ui'
3
+ import {uuid} from '@sanity/uuid'
3
4
  import LanguagesList from 'iso-639-1'
4
5
  import {Dispatch} from 'react'
5
-
6
- import {uuid} from '@sanity/uuid'
7
6
  import {FormField} from 'sanity'
8
- import {SUPPORTED_MUX_LANGUAGES, UploadTextTrack, isCustomTextTrack} from '../util/types'
9
- import FileInputArea from './FileInputArea'
7
+
8
+ import {type PluginConfig, SUPPORTED_MUX_LANGUAGES, UploadTextTrack} from '../util/types'
10
9
 
11
10
  const ALL_LANGUAGE_CODES = LanguagesList.getAllCodes().map((code) => ({
12
11
  value: code,
@@ -25,190 +24,94 @@ const SUBTITLE_LANGUAGES: Record<
25
24
  captions: ALL_LANGUAGE_CODES,
26
25
  }
27
26
 
28
- /**
29
- * Subtitles and Captions are uploaded via .srt and .vtt files, which we can't currently support
30
- * due to the lack of a server to receive Mux's requests to these files' URLs.
31
- *
32
- * For now, only auto-generated subtitles are supported.
33
- */
34
- const TRACK_TYPES = [
35
- {value: 'autogenerated', label: 'Auto-generated Subtitles'},
36
- // {value: 'subtitles', label: 'Subtitles'},
37
- // {value: 'captions', label: 'Closed Captions'},
38
- ] as const
39
-
40
27
  type TrackSubAction =
41
- | {subAction: 'add'}
28
+ | {subAction: 'add'; value: Partial<UploadTextTrack>}
42
29
  | {subAction: 'update'; value: Partial<UploadTextTrack>}
43
30
  | {subAction: 'delete'}
44
31
 
45
32
  export type TrackAction = {action: 'track'; id: string} & TrackSubAction
46
33
 
47
- /**
48
- * Handles editing of a single text track, dispatching actions back to the
49
- * parent UploadConfiguration state object for changing internal state.
50
- */
51
- function TrackEditor({
52
- canAutoGenerate,
53
- track,
54
- dispatch,
55
- }: {
56
- canAutoGenerate: boolean
57
- track: Partial<UploadTextTrack> & {_id: string}
58
- dispatch: Dispatch<TrackAction>
59
- }) {
60
- const {_id: id, type} = track
61
- const dispatchTrackAction = (args: TrackSubAction) => dispatch({action: 'track', id, ...args})
62
-
63
- const trackTypes = TRACK_TYPES.filter(
64
- ({value}) => !(value === 'autogenerated' && !canAutoGenerate)
65
- )
66
-
67
- if (trackTypes.length === 0) return null
68
-
69
- return (
70
- <Card border padding={3} radius={2} style={{position: 'relative'}}>
71
- <Stack space={3}>
72
- {trackTypes.length > 1 && (
73
- <FormField title="Auto-generated subtitles">
74
- <Flex gap={3}>
75
- {trackTypes.map(({value, label}) => {
76
- const inputId = `${id}--type-${value}`
77
- return (
78
- <Flex key={value} align="center" gap={2}>
79
- <Radio
80
- checked={type === value}
81
- name="track-type"
82
- onChange={(e) =>
83
- dispatchTrackAction({
84
- subAction: 'update',
85
- value: {
86
- type: e.currentTarget.value as UploadTextTrack['type'],
87
- },
88
- })
89
- }
90
- value={value}
91
- id={inputId}
92
- />
93
- <Text as="label" htmlFor={inputId}>
94
- {label}
95
- </Text>
96
- </Flex>
97
- )
98
- })}
99
- </Flex>
100
- </FormField>
101
- )}
102
-
103
- <Autocomplete
104
- id={`${id}--language`}
105
- value={track.language_code}
106
- onChange={(newValue) =>
107
- dispatchTrackAction({
108
- subAction: 'update',
109
- value: {
110
- language_code: newValue,
111
- name: LanguagesList.getNativeName(newValue),
112
- },
113
- })
114
- }
115
- options={SUBTITLE_LANGUAGES[track.type!]}
116
- icon={TranslateIcon}
117
- placeholder="Select language"
118
- filterOption={(query, option) =>
119
- option.label.toLowerCase().indexOf(query.toLowerCase()) > -1 ||
120
- option.value.toLowerCase().indexOf(query.toLowerCase()) > -1
121
- }
122
- openButton
123
- renderValue={(value) =>
124
- SUBTITLE_LANGUAGES[track.type!].find((l) => l.value === value)?.label || value
125
- }
126
- renderOption={(option) => (
127
- <Card data-as="button" padding={3} radius={2} tone="inherit">
128
- <Text size={2} textOverflow="ellipsis">
129
- {option.label} ({option.value})
130
- </Text>
131
- </Card>
132
- )}
133
- />
134
-
135
- <Flex>
136
- <Button
137
- icon={TrashIcon}
138
- tone="critical"
139
- mode="ghost"
140
- onClick={() => dispatchTrackAction({subAction: 'delete'})}
141
- text="Delete"
142
- />
143
- </Flex>
144
- </Stack>
145
- </Card>
146
- )
147
- }
148
-
149
34
  export default function TextTracksEditor({
150
- canAutoGenerate,
151
35
  tracks,
152
36
  dispatch,
37
+ defaultLang,
153
38
  }: {
154
- canAutoGenerate: boolean
39
+ /**
40
+ * Although the schema for tracks is an array, which we'll eventually support to allow uploading
41
+ * multiple custom subtitles, for now we only support a single auto-generated track.
42
+ */
155
43
  tracks: (Partial<UploadTextTrack> & {_id: string})[]
156
44
  dispatch: Dispatch<TrackAction>
45
+ defaultLang: PluginConfig['defaultAutogeneratedSubtitleLang']
157
46
  }) {
158
- const trackTypes = TRACK_TYPES.filter(
159
- ({value}) => !(value === 'autogenerated' && !canAutoGenerate)
160
- )
161
-
162
- if (trackTypes.length === 0) return null
163
-
47
+ const track = tracks[0]
164
48
  return (
165
- <FormField
166
- title="Captions & Subtitles"
167
- description="Provide text tracks for video accessibility."
168
- >
49
+ <FormField title="Auto-generated subtitle or caption">
169
50
  <Stack space={2}>
170
- {tracks.map((track) => (
171
- <TrackEditor
172
- key={track._id}
173
- canAutoGenerate={canAutoGenerate}
174
- track={track}
175
- dispatch={dispatch}
51
+ <Flex align="center">
52
+ <Checkbox
53
+ id="include-autogenerated-track"
54
+ style={{display: 'block'}}
55
+ checked={!!track?.language_code}
56
+ onChange={() => {
57
+ if (track) {
58
+ dispatch({action: 'track', id: track._id, subAction: 'delete'})
59
+ } else {
60
+ dispatch({
61
+ action: 'track',
62
+ id: uuid(),
63
+ subAction: 'add',
64
+ value: {
65
+ type: 'autogenerated',
66
+ name: defaultLang || undefined,
67
+ language_code: defaultLang || undefined,
68
+ },
69
+ })
70
+ }
71
+ }}
72
+ />
73
+ <Box flex={1} paddingLeft={3}>
74
+ <Text>
75
+ <label htmlFor="checkbox">Generate captions</label>
76
+ </Text>
77
+ </Box>
78
+ </Flex>
79
+ {track && (
80
+ <Autocomplete
81
+ id={`text-tract-editor--language`}
82
+ value={track.language_code}
83
+ onChange={(newValue) =>
84
+ dispatch({
85
+ action: 'track',
86
+ id: track._id,
87
+ subAction: 'update',
88
+ value: {
89
+ language_code: newValue,
90
+ name: LanguagesList.getNativeName(newValue),
91
+ },
92
+ })
93
+ }
94
+ options={SUBTITLE_LANGUAGES[track.type!]}
95
+ icon={TranslateIcon}
96
+ placeholder="Select language"
97
+ filterOption={(query, option) =>
98
+ option.label.toLowerCase().indexOf(query.toLowerCase()) > -1 ||
99
+ option.value.toLowerCase().indexOf(query.toLowerCase()) > -1
100
+ }
101
+ openButton
102
+ renderValue={(value) =>
103
+ SUBTITLE_LANGUAGES[track.type!].find((l) => l.value === value)?.label || value
104
+ }
105
+ renderOption={(option) => (
106
+ <Card data-as="button" padding={3} radius={2} tone="inherit">
107
+ <Text size={2} textOverflow="ellipsis">
108
+ {option.label} ({option.value})
109
+ </Text>
110
+ </Card>
111
+ )}
176
112
  />
177
- ))}
178
- <Button
179
- icon={AddIcon}
180
- onClick={() => dispatch({action: 'track', id: uuid(), subAction: 'add'})}
181
- text="New caption/subtitle"
182
- mode="ghost"
183
- />
113
+ )}
184
114
  </Stack>
185
115
  </FormField>
186
116
  )
187
117
  }
188
-
189
- function getFileTextContents(file: File) {
190
- return new Promise<string>((resolve, reject) => {
191
- const reader = new FileReader()
192
-
193
- reader.onload = () => {
194
- if (typeof reader.result === 'string') {
195
- resolve(reader.result)
196
- } else {
197
- reject(new Error('Could not read file'))
198
- }
199
- }
200
-
201
- reader.onerror = reject
202
-
203
- reader.readAsText(file)
204
- })
205
- }
206
-
207
- async function fileToTrackFile(file: File) {
208
- return {
209
- name: file.name,
210
- size: file.size,
211
- type: file.type,
212
- contents: await getFileTextContents(file),
213
- }
214
- }
@@ -1,5 +1,5 @@
1
1
  import {DocumentVideoIcon, UploadIcon} from '@sanity/icons'
2
- import {Button, Card, Checkbox, Dialog, Flex, Label, Radio, Stack, Text} from '@sanity/ui'
2
+ import {Box, Button, Card, Checkbox, Dialog, Flex, Label, Radio, Stack, Text} from '@sanity/ui'
3
3
  import {uuid} from '@sanity/uuid'
4
4
  import LanguagesList from 'iso-639-1'
5
5
  import {useEffect, useId, useReducer, useRef} from 'react'
@@ -61,17 +61,16 @@ export default function UploadConfiguration({
61
61
  }) {
62
62
  const id = useId()
63
63
  const autoTextTracks = useRef<NonNullable<UploadConfig['text_tracks']>>(
64
- (pluginConfig.encoding_tier === 'smart' &&
65
- pluginConfig.defaultAutogeneratedSubtitleLangs?.map(
66
- (language_code) =>
67
- ({
64
+ pluginConfig.encoding_tier === 'smart' && pluginConfig.defaultAutogeneratedSubtitleLang
65
+ ? [
66
+ {
68
67
  _id: uuid(),
69
68
  type: 'autogenerated',
70
- language_code,
71
- name: LanguagesList.getNativeName(language_code),
72
- }) satisfies AutogeneratedTextTrack
73
- )) ||
74
- []
69
+ language_code: pluginConfig.defaultAutogeneratedSubtitleLang,
70
+ name: LanguagesList.getNativeName(pluginConfig.defaultAutogeneratedSubtitleLang),
71
+ } satisfies AutogeneratedTextTrack,
72
+ ]
73
+ : []
75
74
  ).current
76
75
 
77
76
  const [config, dispatch] = useReducer(
@@ -109,11 +108,10 @@ export default function UploadConfiguration({
109
108
  case 'add':
110
109
  // Exit early if track already exists
111
110
  if (target_track_i !== -1) break
112
- text_tracks.push(
113
- (prev.encoding_tier === 'smart'
114
- ? {_id: action.id, type: 'autogenerated'}
115
- : {_id: action.id, type: 'subtitles'}) as UploadTextTrack
116
- )
111
+ text_tracks.push({
112
+ _id: action.id,
113
+ ...action.value,
114
+ } as AutogeneratedTextTrack)
117
115
  break
118
116
  case 'update':
119
117
  if (target_track_i === -1) break
@@ -332,20 +330,22 @@ export default function UploadConfiguration({
332
330
  </Stack>
333
331
  )}
334
332
 
335
- {!disableTextTrackConfig && (
333
+ {!disableTextTrackConfig && config.encoding_tier === 'smart' && (
336
334
  <TextTracksEditor
337
- canAutoGenerate={config.encoding_tier === 'smart'}
338
335
  tracks={config.text_tracks}
339
336
  dispatch={dispatch}
337
+ defaultLang={pluginConfig.defaultAutogeneratedSubtitleLang}
340
338
  />
341
339
  )}
342
340
 
343
- <Button
344
- icon={UploadIcon}
345
- text="Upload"
346
- tone="positive"
347
- onClick={() => startUpload(formatUploadConfig(config))}
348
- />
341
+ <Box marginTop={4}>
342
+ <Button
343
+ icon={UploadIcon}
344
+ text="Upload"
345
+ tone="positive"
346
+ onClick={() => startUpload(formatUploadConfig(config))}
347
+ />
348
+ </Box>
349
349
  </Stack>
350
350
  </Dialog>
351
351
  )
@@ -8,7 +8,7 @@ import {getPlaybackPolicy} from '../util/getPlaybackPolicy'
8
8
  import {VideoAssetDocument} from '../util/types'
9
9
  import IconInfo from './IconInfo'
10
10
  import VideoMetadata from './VideoMetadata'
11
- import VideoPlayer from './VideoPlayer'
11
+ import VideoPlayer, {assetIsAudio} from './VideoPlayer'
12
12
  import VideoThumbnail from './VideoThumbnail'
13
13
 
14
14
  const PlayButton = styled.button`
@@ -143,7 +143,26 @@ export default function VideoInBrowser({
143
143
  <div data-play>
144
144
  <PlayIcon />
145
145
  </div>
146
- <VideoThumbnail asset={asset} />
146
+ {assetIsAudio(asset) ? (
147
+ <div
148
+ style={{
149
+ aspectRatio: THUMBNAIL_ASPECT_RATIO,
150
+ display: 'flex',
151
+ alignItems: 'center',
152
+ justifyContent: 'center',
153
+ }}
154
+ >
155
+ <svg xmlns="http://www.w3.org/2000/svg" width="3em" viewBox="0 0 24 24">
156
+ <path
157
+ fill="currentColor"
158
+ style={{opacity: '0.65'}}
159
+ d="M10.75 19q.95 0 1.6-.65t.65-1.6V13h3v-2h-4v3.875q-.275-.2-.587-.288t-.663-.087q-.95 0-1.6.65t-.65 1.6t.65 1.6t1.6.65M6 22q-.825 0-1.412-.587T4 20V4q0-.825.588-1.412T6 2h8l6 6v12q0 .825-.587 1.413T18 22zm7-13V4H6v16h12V9zM6 4v5zv16z"
160
+ />
161
+ </svg>
162
+ </div>
163
+ ) : (
164
+ <VideoThumbnail asset={asset} />
165
+ )}
147
166
  </PlayButton>
148
167
  )}
149
168
  <VideoMetadata asset={asset} />
@@ -4,7 +4,7 @@ import {Card, Text} from '@sanity/ui'
4
4
  import {type PropsWithChildren, useMemo} from 'react'
5
5
 
6
6
  import {useClient} from '../hooks/useClient'
7
- import {MIN_ASPECT_RATIO} from '../util/constants'
7
+ import {AUDIO_ASPECT_RATIO, MIN_ASPECT_RATIO} from '../util/constants'
8
8
  import {getVideoSrc} from '../util/getVideoSrc'
9
9
  import type {VideoAssetDocument} from '../util/types'
10
10
 
@@ -17,6 +17,8 @@ export default function VideoPlayer({
17
17
  >) {
18
18
  const client = useClient()
19
19
 
20
+ const isAudio = assetIsAudio(asset)
21
+
20
22
  const {src: videoSrc, error} = useMemo(() => {
21
23
  try {
22
24
  const src = asset?.playbackId && getVideoSrc({client, asset})
@@ -41,7 +43,13 @@ export default function VideoPlayer({
41
43
  const [width, height] = (asset?.data?.aspect_ratio ?? '16:9').split(':').map(Number)
42
44
  const targetAspectRatio =
43
45
  props.forceAspectRatio || (Number.isNaN(width) ? 16 / 9 : width / height)
44
- const aspectRatio = Math.max(MIN_ASPECT_RATIO, targetAspectRatio)
46
+ let aspectRatio = Math.max(MIN_ASPECT_RATIO, targetAspectRatio)
47
+ if (isAudio) {
48
+ aspectRatio = props.forceAspectRatio
49
+ ? // Make it wider when forcing aspect ratio to balance with videos' rendering height (audio players overflow a bit)
50
+ props.forceAspectRatio * 1.2
51
+ : AUDIO_ASPECT_RATIO
52
+ }
45
53
 
46
54
  return (
47
55
  <Card tone="transparent" style={{aspectRatio: aspectRatio, position: 'relative'}}>
@@ -63,6 +71,7 @@ export default function VideoPlayer({
63
71
  player_version: process.env.PKG_VERSION,
64
72
  page_type: 'Preview Player',
65
73
  }}
74
+ audio={isAudio}
66
75
  style={{
67
76
  height: '100%',
68
77
  width: '100%',
@@ -94,3 +103,7 @@ export default function VideoPlayer({
94
103
  </Card>
95
104
  )
96
105
  }
106
+
107
+ export function assetIsAudio(asset: VideoAssetDocument) {
108
+ return asset.data?.max_stored_resolution === 'Audio only'
109
+ }
@@ -58,7 +58,7 @@ export default function useImportMuxAssets() {
58
58
 
59
59
  async function importAssets() {
60
60
  setImportState('importing')
61
- const documents = selectedAssets.map(muxAssetToSanityDocument)
61
+ const documents = selectedAssets.flatMap((asset) => muxAssetToSanityDocument(asset) || [])
62
62
 
63
63
  const tx = client.transaction()
64
64
  documents.forEach((doc) => tx.create(doc))
@@ -89,14 +89,18 @@ export default function useImportMuxAssets() {
89
89
  }
90
90
  }
91
91
 
92
- function muxAssetToSanityDocument(asset: MuxAsset): VideoAssetDocument {
92
+ function muxAssetToSanityDocument(asset: MuxAsset): VideoAssetDocument | undefined {
93
+ const playbackId = (asset.playback_ids || []).find((p) => p.id)?.id
94
+
95
+ if (!playbackId) return undefined
96
+
93
97
  return {
94
98
  _id: uuid(),
95
99
  _type: 'mux.videoAsset',
96
100
  _updatedAt: new Date().toISOString(),
97
101
  _createdAt: parseMuxDate(asset.created_at).toISOString(),
98
102
  assetId: asset.id,
99
- playbackId: asset.playback_ids.find((p) => p.id)?.id,
103
+ playbackId,
100
104
  filename: `Asset #${truncateString(asset.id, 15)}`,
101
105
  status: asset.status,
102
106
  data: asset,