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.
Files changed (48) hide show
  1. package/README.md +25 -24
  2. package/dist/index.d.mts +13 -1
  3. package/dist/index.d.ts +13 -1
  4. package/dist/index.js +771 -351
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +773 -353
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +1 -1
  9. package/src/_exports/index.ts +1 -0
  10. package/src/actions/secrets.ts +6 -1
  11. package/src/actions/upload.ts +1 -1
  12. package/src/components/ConfigureApi.tsx +51 -5
  13. package/src/components/EditCaptionDialog.tsx +2 -2
  14. package/src/components/InputBrowser.tsx +8 -2
  15. package/src/components/PageSelector.tsx +4 -7
  16. package/src/components/Player.styled.tsx +7 -2
  17. package/src/components/PlayerActionsMenu.tsx +1 -1
  18. package/src/components/SelectAsset.tsx +9 -3
  19. package/src/components/StudioTool.tsx +2 -2
  20. package/src/components/UploadConfiguration.tsx +104 -343
  21. package/src/components/Uploader.tsx +18 -7
  22. package/src/components/VideoDetails/VideoDetails.tsx +28 -8
  23. package/src/components/VideoInBrowser.tsx +53 -6
  24. package/src/components/VideoPlayer.tsx +120 -47
  25. package/src/components/VideoThumbnail.tsx +84 -72
  26. package/src/components/VideosBrowser.tsx +7 -5
  27. package/src/components/uploadConfiguration/PlaybackPolicy.tsx +95 -6
  28. package/src/components/uploadConfiguration/PlaybackPolicyOption.tsx +26 -10
  29. package/src/components/uploadConfiguration/ResolutionTierSelector.tsx +71 -0
  30. package/src/components/uploadConfiguration/StaticRenditionSelector.tsx +179 -0
  31. package/src/context/DrmPlaybackWarningContext.tsx +93 -0
  32. package/src/hooks/useFetchFileSize.ts +54 -0
  33. package/src/hooks/useMediaMetadata.ts +100 -0
  34. package/src/hooks/useSaveSecrets.ts +10 -3
  35. package/src/hooks/useSecretsDocumentValues.ts +9 -1
  36. package/src/hooks/useSecretsFormState.ts +6 -3
  37. package/src/util/asserters.ts +14 -0
  38. package/src/util/createUrlParamsObject.ts +7 -3
  39. package/src/util/generateJwt.ts +11 -2
  40. package/src/util/getPlaybackPolicy.ts +63 -4
  41. package/src/util/getStoryboardSrc.ts +7 -3
  42. package/src/util/getVideoMetadata.ts +1 -0
  43. package/src/util/getVideoSrc.ts +9 -9
  44. package/src/util/readSecrets.ts +3 -1
  45. package/src/util/textTracks.ts +6 -3
  46. package/src/util/tryWithSuspend.ts +22 -0
  47. package/src/util/types.ts +27 -2
  48. package/src/util/getPlaybackId.ts +0 -9
@@ -0,0 +1,100 @@
1
+ import {useEffect, useState} from 'react'
2
+
3
+ import {StagedUpload} from '../components/Uploader'
4
+
5
+ export interface VideoAssetMetadata {
6
+ width?: number
7
+ height?: number
8
+ isAudioOnly?: boolean
9
+ duration?: number
10
+ size?: number
11
+ }
12
+
13
+ export function useMediaMetadata(stagedUpload: StagedUpload) {
14
+ const [videoAssetMetadata, setVideoAssetMetadata] = useState<VideoAssetMetadata | null>(null)
15
+ const [isLoadingMetadata, setIsLoadingMetadata] = useState(false)
16
+ useEffect(() => {
17
+ let videoSrc = null
18
+ // Validate file uploads
19
+ if (stagedUpload.type === 'file') {
20
+ const file = stagedUpload.files[0]
21
+ videoSrc = URL.createObjectURL(file)
22
+ }
23
+
24
+ // Validate URL uploads
25
+ if (stagedUpload.type === 'url') {
26
+ videoSrc = stagedUpload.url
27
+ }
28
+
29
+ setVideoAssetMetadata((old) => ({
30
+ ...old,
31
+ duration: undefined,
32
+ width: undefined,
33
+ height: undefined,
34
+ }))
35
+
36
+ if (!videoSrc) return () => null
37
+
38
+ setIsLoadingMetadata(true)
39
+ const videoElement = document.createElement('video')
40
+ videoElement.preload = 'metadata'
41
+
42
+ const metadataListeners = [
43
+ () => {
44
+ setIsLoadingMetadata(false)
45
+ },
46
+ () => {
47
+ const duration = videoElement.duration
48
+ const width = videoElement.videoWidth
49
+ const height = videoElement.videoHeight
50
+ const isAudioOnly = width <= 0 && height <= 0
51
+ setVideoAssetMetadata((old) => {
52
+ return {
53
+ ...old,
54
+ duration: duration,
55
+ width: width,
56
+ height: height,
57
+ isAudioOnly: isAudioOnly,
58
+ }
59
+ })
60
+ },
61
+ ]
62
+
63
+ const cleanupVideo = (videoEl: HTMLVideoElement) => {
64
+ const currentVideoSrc = videoEl?.src
65
+ if (videoEl) {
66
+ metadataListeners.forEach((listener) =>
67
+ videoEl.removeEventListener('loadedmetadata', listener)
68
+ )
69
+ videoEl.onerror = null
70
+ videoEl.src = ''
71
+ videoEl.load()
72
+ }
73
+ if (currentVideoSrc?.startsWith('blob:')) {
74
+ URL.revokeObjectURL(currentVideoSrc)
75
+ }
76
+ }
77
+ metadataListeners.push(() => setTimeout(() => cleanupVideo(videoElement), 0))
78
+
79
+ videoElement.onerror = () => {
80
+ setIsLoadingMetadata(false)
81
+ console.warn('Could not read video metadata for validation')
82
+ cleanupVideo(videoElement)
83
+ }
84
+
85
+ metadataListeners.forEach((listener) =>
86
+ videoElement.addEventListener('loadedmetadata', listener)
87
+ )
88
+ videoElement.src = videoSrc
89
+
90
+ return () => {
91
+ cleanupVideo(videoElement)
92
+ }
93
+ }, [stagedUpload.type, stagedUpload])
94
+
95
+ return {
96
+ videoAssetMetadata,
97
+ setVideoAssetMetadata,
98
+ isLoadingMetadata,
99
+ }
100
+ }
@@ -10,7 +10,11 @@ export const useSaveSecrets = (client: SanityClient, secrets: Secrets) => {
10
10
  token,
11
11
  secretKey,
12
12
  enableSignedUrls,
13
- }: Pick<Secrets, 'token' | 'secretKey' | 'enableSignedUrls'>): Promise<Secrets> => {
13
+ drmConfigId,
14
+ }: Pick<
15
+ Secrets,
16
+ 'token' | 'secretKey' | 'enableSignedUrls' | 'drmConfigId'
17
+ >): Promise<Secrets> => {
14
18
  let {signingKeyId, signingKeyPrivate} = secrets
15
19
 
16
20
  try {
@@ -20,7 +24,8 @@ export const useSaveSecrets = (client: SanityClient, secrets: Secrets) => {
20
24
  secretKey!,
21
25
  enableSignedUrls,
22
26
  signingKeyId!,
23
- signingKeyPrivate!
27
+ signingKeyPrivate!,
28
+ drmConfigId!
24
29
  )
25
30
  const valid = await testSecrets(client)
26
31
  if (!valid?.status && token && secretKey) {
@@ -49,7 +54,8 @@ export const useSaveSecrets = (client: SanityClient, secrets: Secrets) => {
49
54
  secretKey!,
50
55
  enableSignedUrls,
51
56
  signingKeyId,
52
- signingKeyPrivate
57
+ signingKeyPrivate,
58
+ drmConfigId ?? ''
53
59
  )
54
60
  } catch (err: any) {
55
61
  // eslint-disable-next-line no-console
@@ -64,6 +70,7 @@ export const useSaveSecrets = (client: SanityClient, secrets: Secrets) => {
64
70
  enableSignedUrls,
65
71
  signingKeyId,
66
72
  signingKeyPrivate,
73
+ drmConfigId,
67
74
  }
68
75
  },
69
76
  [client, secrets]
@@ -4,7 +4,14 @@ import {useDocumentValues} from 'sanity'
4
4
  import {muxSecretsDocumentId} from '../util/constants'
5
5
  import type {Secrets} from '../util/types'
6
6
 
7
- const path = ['token', 'secretKey', 'enableSignedUrls', 'signingKeyId', 'signingKeyPrivate']
7
+ const path = [
8
+ 'token',
9
+ 'secretKey',
10
+ 'enableSignedUrls',
11
+ 'signingKeyId',
12
+ 'signingKeyPrivate',
13
+ 'drmConfigId',
14
+ ]
8
15
  export const useSecretsDocumentValues = () => {
9
16
  const {error, isLoading, value} = useDocumentValues<Partial<Secrets> | null | undefined>(
10
17
  muxSecretsDocumentId,
@@ -18,6 +25,7 @@ export const useSecretsDocumentValues = () => {
18
25
  enableSignedUrls: value?.enableSignedUrls || false,
19
26
  signingKeyId: value?.signingKeyId || null,
20
27
  signingKeyPrivate: value?.signingKeyPrivate || null,
28
+ drmConfigId: value?.drmConfigId || null,
21
29
  }
22
30
  return {
23
31
  isInitialSetup: !exists,
@@ -2,7 +2,8 @@ import {useReducer} from 'react'
2
2
 
3
3
  import type {Secrets} from '../util/types'
4
4
 
5
- export interface State extends Pick<Secrets, 'token' | 'secretKey' | 'enableSignedUrls'> {
5
+ export interface State
6
+ extends Pick<Secrets, 'token' | 'secretKey' | 'enableSignedUrls' | 'drmConfigId'> {
6
7
  submitting: boolean
7
8
  error: string | null
8
9
  }
@@ -13,7 +14,8 @@ export type Action =
13
14
  | {type: 'change'; payload: {name: 'token'; value: string}}
14
15
  | {type: 'change'; payload: {name: 'secretKey'; value: string}}
15
16
  | {type: 'change'; payload: {name: 'enableSignedUrls'; value: boolean}}
16
- function init({token, secretKey, enableSignedUrls}: Secrets): State {
17
+ | {type: 'change'; payload: {name: 'drmConfigId'; value: string}}
18
+ function init({token, secretKey, enableSignedUrls, drmConfigId}: Secrets): State {
17
19
  return {
18
20
  submitting: false,
19
21
  error: null,
@@ -22,6 +24,7 @@ function init({token, secretKey, enableSignedUrls}: Secrets): State {
22
24
  token: token ?? '',
23
25
  secretKey: secretKey ?? '',
24
26
  enableSignedUrls: enableSignedUrls ?? false,
27
+ drmConfigId: drmConfigId ?? '',
25
28
  }
26
29
  }
27
30
  function reducer(state: State, action: Action) {
@@ -35,7 +38,7 @@ function reducer(state: State, action: Action) {
35
38
  case 'change':
36
39
  return {...state, [action.payload.name]: action.payload.value}
37
40
  default:
38
- throw new Error(`Unknown action type: ${(action as any)?.type}`)
41
+ throw new Error(`Unknown action type: ${(action as unknown as Action)?.type}`)
39
42
  }
40
43
  }
41
44
 
@@ -1,3 +1,4 @@
1
+ import {ServerError} from '@sanity/client'
1
2
  import {type InputProps, isObjectInputProps, type PreviewLayoutKey, type PreviewProps} from 'sanity'
2
3
 
3
4
  import type {MuxInputPreviewProps, MuxInputProps} from './types'
@@ -20,3 +21,16 @@ export function isValidUrl(url: string): boolean {
20
21
  return false
21
22
  }
22
23
  }
24
+
25
+ /**
26
+ * We consider a server error one with status code 5XX.
27
+ * Used mainly to handle unknown Proxy issues.
28
+ */
29
+ export function isServerError(error: Error): error is ServerError {
30
+ return (
31
+ 'statusCode' in error &&
32
+ typeof error.statusCode === 'number' &&
33
+ 500 <= error.statusCode &&
34
+ error.statusCode <= 600
35
+ )
36
+ }
@@ -1,10 +1,13 @@
1
1
  import type {SanityClient} from 'sanity'
2
2
 
3
+ import {getPlaybackId} from '../util/getPlaybackPolicy'
3
4
  import {Audience, generateJwt} from './generateJwt'
4
- import {getPlaybackId} from './getPlaybackId'
5
- import {getPlaybackPolicy} from './getPlaybackPolicy'
5
+ import {getPlaybackPolicyById} from './getPlaybackPolicy'
6
6
  import type {AssetThumbnailOptions} from './types'
7
7
 
8
+ /**
9
+ * May throw a Promise. Call this with {@link tryWithSuspend} or rethrow the Promise
10
+ */
8
11
  export function createUrlParamsObject(
9
12
  client: SanityClient,
10
13
  asset: AssetThumbnailOptions['asset'],
@@ -16,7 +19,8 @@ export function createUrlParamsObject(
16
19
  let searchParams = new URLSearchParams(
17
20
  JSON.parse(JSON.stringify(params, (_, v) => v ?? undefined))
18
21
  )
19
- if (getPlaybackPolicy(asset) === 'signed') {
22
+ const playbackPolicy = getPlaybackPolicyById(asset, playbackId)?.policy
23
+ if (playbackPolicy === 'signed' || playbackPolicy === 'drm') {
20
24
  const token = generateJwt(client, playbackId, audience, params)
21
25
  searchParams = new URLSearchParams({token})
22
26
  }
@@ -4,7 +4,7 @@ import {suspend} from 'suspend-react'
4
4
  import {readSecrets} from './readSecrets'
5
5
  import type {AnimatedThumbnailOptions, ThumbnailOptions} from './types'
6
6
 
7
- export type Audience = 'g' | 's' | 't' | 'v'
7
+ export type Audience = 'g' | 's' | 't' | 'v' | 'd'
8
8
 
9
9
  export type Payload<T extends Audience> = T extends 'g'
10
10
  ? AnimatedThumbnailOptions
@@ -14,8 +14,13 @@ export type Payload<T extends Audience> = T extends 'g'
14
14
  ? ThumbnailOptions
15
15
  : T extends 'v'
16
16
  ? never
17
- : never
17
+ : T extends 'd'
18
+ ? never
19
+ : never
18
20
 
21
+ /**
22
+ * Uses suspend. Call this with {@link tryWithSuspend} or rethrow the Promise
23
+ */
19
24
  export function generateJwt<T extends Audience>(
20
25
  client: SanityClient,
21
26
  playbackId: string,
@@ -30,6 +35,10 @@ export function generateJwt<T extends Audience>(
30
35
  throw new TypeError("Missing `signingKeyPrivate`.\n Check your plugin's configuration")
31
36
  }
32
37
 
38
+ /* Using suspend means we need to use Suspense on parent components.
39
+ Also, this will throw a Promise under the hood (apparently common in React),
40
+ so if we want to catch errors we have to take this into account in catch blocks
41
+ and rethrow promises. */
33
42
  // @ts-expect-error - handle missing typings for this package
34
43
  const {default: sign} = suspend(() => import('jsonwebtoken-esm/sign'), ['jsonwebtoken-esm/sign'])
35
44
 
@@ -1,10 +1,69 @@
1
- import type {PlaybackPolicy, VideoAssetDocument} from './types'
1
+ import type {
2
+ AdvancedPlaybackPolicy,
3
+ MuxPlaybackId,
4
+ PlaybackPolicy,
5
+ VideoAssetDocument,
6
+ } from './types'
7
+
8
+ /* - Returns the playback id of the asset based on the specified priority.
9
+ By default chooses the "strongest" policy
10
+ - Otherwise, returns the first playback id in the array.
11
+ */
12
+ export function getPlaybackId(
13
+ asset: Pick<VideoAssetDocument, 'data'>,
14
+ priority: string[] = ['drm', 'signed', 'public']
15
+ ): string {
16
+ try {
17
+ if (!asset) {
18
+ throw new TypeError('Tried to get playback Id with no asset')
19
+ }
20
+
21
+ const playbackIds = asset.data?.playback_ids
22
+ if (playbackIds && playbackIds.length > 0) {
23
+ for (const policy of priority) {
24
+ const match = playbackIds.find((entry) => entry.policy === policy)
25
+ if (match) {
26
+ return match.id
27
+ }
28
+ }
29
+
30
+ return playbackIds[0].id
31
+ }
32
+
33
+ throw new TypeError('Missing playbackId')
34
+ } catch (e) {
35
+ console.error('Asset is missing a playbackId', {asset}, e)
36
+ throw e
37
+ }
38
+ }
2
39
 
3
40
  export function getPlaybackPolicy(
4
41
  asset: Pick<VideoAssetDocument, 'data' | 'playbackId'>
5
- ): PlaybackPolicy {
42
+ ): MuxPlaybackId | undefined {
43
+ return (
44
+ asset.data?.playback_ids?.find(
45
+ (playbackId) => getPlaybackId(asset, ['drm', 'signed', 'public']) === playbackId.id
46
+ ) ?? {id: '', policy: 'public'}
47
+ )
48
+ }
49
+
50
+ export function getPlaybackPolicyById(
51
+ asset: Pick<VideoAssetDocument, 'data'>,
52
+ playbackId: string
53
+ ): MuxPlaybackId | undefined {
54
+ return asset.data?.playback_ids?.find((entry) => playbackId === entry.id)
55
+ }
56
+
57
+ export function hasPlaybackPolicy(
58
+ data: Partial<{
59
+ playback_policy?: PlaybackPolicy[]
60
+ advanced_playback_policies: AdvancedPlaybackPolicy[]
61
+ }>,
62
+ policy: PlaybackPolicy
63
+ ) {
6
64
  return (
7
- asset.data?.playback_ids?.find((playbackId) => asset.playbackId === playbackId.id)?.policy ??
8
- 'public'
65
+ (data.advanced_playback_policies &&
66
+ data.advanced_playback_policies.find((p) => p.policy === policy)) ||
67
+ data.playback_policy?.find((p) => p === policy)
9
68
  )
10
69
  }
@@ -1,8 +1,8 @@
1
1
  import type {SanityClient} from 'sanity'
2
2
 
3
+ import {getPlaybackId} from '../util/getPlaybackPolicy'
3
4
  import {generateJwt} from './generateJwt'
4
- import {getPlaybackId} from './getPlaybackId'
5
- import {getPlaybackPolicy} from './getPlaybackPolicy'
5
+ import {getPlaybackPolicyById} from './getPlaybackPolicy'
6
6
  import type {MuxStoryboardUrl, VideoAssetDocument} from './types'
7
7
 
8
8
  interface StoryboardSrcOptions {
@@ -10,11 +10,15 @@ interface StoryboardSrcOptions {
10
10
  client: SanityClient
11
11
  }
12
12
 
13
+ /**
14
+ * May throw a Promise. Call this with {@link tryWithSuspend} or rethrow the Promise
15
+ */
13
16
  export function getStoryboardSrc({asset, client}: StoryboardSrcOptions): MuxStoryboardUrl {
14
17
  const playbackId = getPlaybackId(asset)
15
18
  const searchParams = new URLSearchParams()
16
19
 
17
- if (getPlaybackPolicy(asset) === 'signed') {
20
+ const playbackPolicy = getPlaybackPolicyById(asset, playbackId)?.policy
21
+ if (playbackPolicy === 'signed' || playbackPolicy === 'drm') {
18
22
  const token = generateJwt(client, playbackId, 's')
19
23
  searchParams.set('token', token)
20
24
  }
@@ -13,6 +13,7 @@ export default function getVideoMetadata(doc: VideoAssetDocument) {
13
13
  playbackId: doc.playbackId,
14
14
  createdAt: date,
15
15
  duration: doc.data?.duration ? formatSeconds(doc.data?.duration) : undefined,
16
+ playback_ids: doc.data?.playback_ids,
16
17
  aspect_ratio: doc.data?.aspect_ratio,
17
18
  max_stored_resolution: doc.data?.max_stored_resolution,
18
19
  max_stored_frame_rate: doc.data?.max_stored_frame_rate,
@@ -1,23 +1,23 @@
1
1
  import type {SanityClient} from 'sanity'
2
2
 
3
3
  import {generateJwt} from './generateJwt'
4
- import {getPlaybackId} from './getPlaybackId'
5
- import {getPlaybackPolicy} from './getPlaybackPolicy'
6
- import type {MuxVideoUrl, VideoAssetDocument} from './types'
4
+ import type {MuxPlaybackId, MuxVideoUrl} from './types'
7
5
 
8
6
  interface VideoSrcOptions {
9
- asset: VideoAssetDocument
7
+ muxPlaybackId: MuxPlaybackId
10
8
  client: SanityClient
11
9
  }
12
10
 
13
- export function getVideoSrc({asset, client}: VideoSrcOptions): MuxVideoUrl {
14
- const playbackId = getPlaybackId(asset)
11
+ /**
12
+ * May throw a Promise. Call this with {@link tryWithSuspend} or rethrow the Promise
13
+ */
14
+ export function getVideoSrc({client, muxPlaybackId}: VideoSrcOptions): MuxVideoUrl {
15
15
  const searchParams = new URLSearchParams()
16
16
 
17
- if (getPlaybackPolicy(asset) === 'signed') {
18
- const token = generateJwt(client, playbackId, 'v')
17
+ if (muxPlaybackId.policy === 'signed' || muxPlaybackId.policy === 'drm') {
18
+ const token = generateJwt(client, muxPlaybackId.id, 'v')
19
19
  searchParams.set('token', token)
20
20
  }
21
21
 
22
- return `https://stream.mux.com/${playbackId}.m3u8?${searchParams}`
22
+ return `https://stream.mux.com/${muxPlaybackId.id}.m3u8?${searchParams}`
23
23
  }
@@ -21,7 +21,8 @@ export function readSecrets(client: SanityClient): Secrets {
21
21
  secretKey,
22
22
  enableSignedUrls,
23
23
  signingKeyId,
24
- signingKeyPrivate
24
+ signingKeyPrivate,
25
+ drmConfigId
25
26
  }`,
26
27
  {_id}
27
28
  )
@@ -31,6 +32,7 @@ export function readSecrets(client: SanityClient): Secrets {
31
32
  enableSignedUrls: Boolean(data?.enableSignedUrls) || false,
32
33
  signingKeyId: data?.signingKeyId || null,
33
34
  signingKeyPrivate: data?.signingKeyPrivate || null,
35
+ drmConfigId: data?.drmConfigId || null,
34
36
  }
35
37
  }, [cacheNs, _id, projectId, dataset])
36
38
  }
@@ -2,7 +2,7 @@ import type {SanityClient} from '@sanity/client'
2
2
 
3
3
  import {getAsset} from '../actions/assets'
4
4
  import {generateJwt} from './generateJwt'
5
- import {getPlaybackId} from './getPlaybackId'
5
+ import {getPlaybackId} from './getPlaybackPolicy'
6
6
  import {getPlaybackPolicy} from './getPlaybackPolicy'
7
7
  import type {MuxTextTrack, VideoAssetDocument} from './types'
8
8
 
@@ -169,6 +169,9 @@ export async function pollTrackStatus(
169
169
  }
170
170
  }
171
171
 
172
+ /**
173
+ * May throw a Promise. Call this with {@link tryWithSuspend} or rethrow the Promise
174
+ */
172
175
  export async function downloadVttFile(
173
176
  client: SanityClient,
174
177
  asset: VideoAssetDocument,
@@ -191,11 +194,11 @@ export async function downloadVttFile(
191
194
  throw new Error('Playback ID is required')
192
195
  }
193
196
 
194
- const playbackPolicy = getPlaybackPolicy(asset)
197
+ const playbackPolicy = getPlaybackPolicy(asset)?.policy
195
198
 
196
199
  let downloadUrl = `https://stream.mux.com/${playbackId}/text/${track.id}.vtt`
197
200
 
198
- if (playbackPolicy === 'signed') {
201
+ if (playbackPolicy === 'signed' || playbackPolicy === 'drm') {
199
202
  const token = generateJwt(client, playbackId, 'v')
200
203
  downloadUrl += `?token=${token}`
201
204
  }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * When running `suspend()` from react-suspend a function may throw a Promise
3
+ * causing unexpected behavior when catching.
4
+ * @param block Your block of code that uses suspend
5
+ * @param onError (optional) How to handle a regular Error
6
+ * @returns Whatever is returned by the block if it succeeds, otherwise whatever is resolved by onError if defined
7
+ * @throws rethrows the caught Promise to comply with Suspense logic
8
+ */
9
+ export function tryWithSuspend<T, E>(
10
+ block: () => T,
11
+ onError?: (error: Error) => E
12
+ ): T | E | undefined {
13
+ try {
14
+ return block()
15
+ } catch (errorOrPromise) {
16
+ if (errorOrPromise instanceof Promise) {
17
+ // react-suspend will throw a Promise
18
+ throw errorOrPromise
19
+ }
20
+ return onError ? onError(errorOrPromise as Error) : undefined
21
+ }
22
+ }
package/src/util/types.ts CHANGED
@@ -89,6 +89,13 @@ export interface MuxInputConfig {
89
89
  */
90
90
  defaultPublic?: boolean
91
91
 
92
+ /**
93
+ * Enables DRM Protection by default, if you configured your DRM Configuration Id.
94
+ * @see {@link https://www.mux.com/docs/guides/protect-videos-with-drm}
95
+ * @defaultValue true
96
+ */
97
+ defaultDrm?: boolean
98
+
92
99
  /**
93
100
  * Auto-generate captions for these languages by default.
94
101
  * Requires `"video_quality": "plus"`
@@ -124,6 +131,13 @@ export interface MuxInputConfig {
124
131
  */
125
132
  disableTextTrackConfig?: boolean
126
133
 
134
+ /**
135
+ * Whether or not to show the playback warning when trying to watch DRM content for the first time.
136
+ *
137
+ * @defaultValue false
138
+ */
139
+ disableDrmPlaybackWarning?: boolean
140
+
127
141
  /**
128
142
  * The mime types that are accepted by the input.
129
143
  *
@@ -266,6 +280,7 @@ export interface UploadConfig
266
280
  text_tracks: UploadTextTrack[]
267
281
  signed_policy: boolean
268
282
  public_policy: boolean
283
+ drm_policy: boolean
269
284
  }
270
285
 
271
286
  /**
@@ -308,18 +323,28 @@ export interface MuxNewAssetSettings
308
323
  }[]
309
324
 
310
325
  /** An array of playback policy names that you want applied to this asset and available through playback_ids. */
311
- playback_policy: ('public' | 'signed' | 'drm')[]
326
+ playback_policy?: PlaybackPolicy[]
327
+
328
+ /** An array of playback policy objects that you want applied to this asset and available through playback_ids. advanced_playback_policies must be used instead of playback_policies when creating a DRM playback ID. */
329
+ advanced_playback_policies: AdvancedPlaybackPolicy[]
312
330
 
313
331
  /** Arbitrary user-supplied metadata that will be included in the asset details and related webhooks. */
314
332
  passthrough?: string
315
333
  }
316
334
 
335
+ /** Used by advanced_playback_policies, allows to define DRM config. */
336
+ export type AdvancedPlaybackPolicy = {
337
+ policy: PlaybackPolicy
338
+ drm_configuration_id?: string
339
+ }
340
+
317
341
  export interface Secrets {
318
342
  token: string | null
319
343
  secretKey: string | null
320
344
  enableSignedUrls: boolean
321
345
  signingKeyId: string | null
322
346
  signingKeyPrivate: string | null
347
+ drmConfigId: string | null
323
348
  }
324
349
 
325
350
  // This narrowed type indicates that there may be assets that are signed, and we have the secrets to access them
@@ -366,7 +391,7 @@ export interface AssetThumbnailOptions {
366
391
  asset: Pick<VideoAssetDocument, 'playbackId' | 'data' | 'thumbTime' | 'filename' | 'assetId'>
367
392
  }
368
393
 
369
- export type PlaybackPolicy = 'signed' | 'public'
394
+ export type PlaybackPolicy = 'signed' | 'public' | 'drm'
370
395
 
371
396
  export interface MuxErrors {
372
397
  type: string
@@ -1,9 +0,0 @@
1
- import type {VideoAssetDocument} from './types'
2
-
3
- export function getPlaybackId(asset: Pick<VideoAssetDocument, 'playbackId'>): string {
4
- if (!asset?.playbackId) {
5
- console.error('Asset is missing a playbackId', {asset})
6
- throw new TypeError(`Missing playbackId`)
7
- }
8
- return asset.playbackId
9
- }