@pro-laico/payload-mux 0.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 (72) hide show
  1. package/LICENSE.md +52 -0
  2. package/README.md +11 -0
  3. package/dist/collections/MuxVideo.d.ts +7 -0
  4. package/dist/collections/MuxVideo.d.ts.map +1 -0
  5. package/dist/collections/MuxVideo.js +266 -0
  6. package/dist/collections/MuxVideo.js.map +1 -0
  7. package/dist/components/MuxUploaderField.d.ts +10 -0
  8. package/dist/components/MuxUploaderField.d.ts.map +1 -0
  9. package/dist/components/MuxUploaderField.js +143 -0
  10. package/dist/components/MuxUploaderField.js.map +1 -0
  11. package/dist/components/MuxVideoGifCell.d.ts +5 -0
  12. package/dist/components/MuxVideoGifCell.d.ts.map +1 -0
  13. package/dist/components/MuxVideoGifCell.js +21 -0
  14. package/dist/components/MuxVideoGifCell.js.map +1 -0
  15. package/dist/components/MuxVideoImageCell.d.ts +5 -0
  16. package/dist/components/MuxVideoImageCell.d.ts.map +1 -0
  17. package/dist/components/MuxVideoImageCell.js +21 -0
  18. package/dist/components/MuxVideoImageCell.js.map +1 -0
  19. package/dist/components/mux-uploader.css +23 -0
  20. package/dist/endpoints/upload.d.ts +10 -0
  21. package/dist/endpoints/upload.d.ts.map +1 -0
  22. package/dist/endpoints/upload.js +49 -0
  23. package/dist/endpoints/upload.js.map +1 -0
  24. package/dist/endpoints/webhook.d.ts +16 -0
  25. package/dist/endpoints/webhook.d.ts.map +1 -0
  26. package/dist/endpoints/webhook.js +148 -0
  27. package/dist/endpoints/webhook.js.map +1 -0
  28. package/dist/hooks/afterDelete.d.ts +6 -0
  29. package/dist/hooks/afterDelete.d.ts.map +1 -0
  30. package/dist/hooks/afterDelete.js +12 -0
  31. package/dist/hooks/afterDelete.js.map +1 -0
  32. package/dist/hooks/beforeChange.d.ts +11 -0
  33. package/dist/hooks/beforeChange.d.ts.map +1 -0
  34. package/dist/hooks/beforeChange.js +95 -0
  35. package/dist/hooks/beforeChange.js.map +1 -0
  36. package/dist/hooks/beforeValidate.d.ts +17 -0
  37. package/dist/hooks/beforeValidate.d.ts.map +1 -0
  38. package/dist/hooks/beforeValidate.js +61 -0
  39. package/dist/hooks/beforeValidate.js.map +1 -0
  40. package/dist/index.d.ts +6 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +8 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/lib/defaultAccess.d.ts +5 -0
  45. package/dist/lib/defaultAccess.d.ts.map +1 -0
  46. package/dist/lib/defaultAccess.js +4 -0
  47. package/dist/lib/defaultAccess.js.map +1 -0
  48. package/dist/lib/delay.d.ts +3 -0
  49. package/dist/lib/delay.d.ts.map +1 -0
  50. package/dist/lib/delay.js +3 -0
  51. package/dist/lib/delay.js.map +1 -0
  52. package/dist/lib/getAssetMetadata.d.ts +34 -0
  53. package/dist/lib/getAssetMetadata.d.ts.map +1 -0
  54. package/dist/lib/getAssetMetadata.js +21 -0
  55. package/dist/lib/getAssetMetadata.js.map +1 -0
  56. package/dist/lib/ingest.d.ts +53 -0
  57. package/dist/lib/ingest.d.ts.map +1 -0
  58. package/dist/lib/ingest.js +109 -0
  59. package/dist/lib/ingest.js.map +1 -0
  60. package/dist/lib/isAllowed.d.ts +7 -0
  61. package/dist/lib/isAllowed.d.ts.map +1 -0
  62. package/dist/lib/isAllowed.js +6 -0
  63. package/dist/lib/isAllowed.js.map +1 -0
  64. package/dist/plugin.d.ts +13 -0
  65. package/dist/plugin.d.ts.map +1 -0
  66. package/dist/plugin.js +80 -0
  67. package/dist/plugin.js.map +1 -0
  68. package/dist/types.d.ts +78 -0
  69. package/dist/types.d.ts.map +1 -0
  70. package/dist/types.js +3 -0
  71. package/dist/types.js.map +1 -0
  72. package/package.json +89 -0
package/LICENSE.md ADDED
@@ -0,0 +1,52 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-2026 Pro Laico LLC <contact@prolaico.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ 'Software'), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ ---
25
+
26
+ ## Third-party notices
27
+
28
+ Portions of this package are derived from
29
+ [`@oversightstudio/mux-video`](https://github.com/oversightstudio/payload-plugins),
30
+ used under the MIT License:
31
+
32
+ MIT License
33
+
34
+ Copyright (c) 2025 OVERSIGHT STUDIO LIMITED
35
+
36
+ Permission is hereby granted, free of charge, to any person obtaining a copy
37
+ of this software and associated documentation files (the "Software"), to deal
38
+ in the Software without restriction, including without limitation the rights
39
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
40
+ copies of the Software, and to permit persons to whom the Software is
41
+ furnished to do so, subject to the following conditions:
42
+
43
+ The above copyright notice and this permission notice shall be included in all
44
+ copies or substantial portions of the Software.
45
+
46
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
47
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
48
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
49
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
50
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
51
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
52
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # @pro-laico/payload-mux
2
+
3
+ [Mux Video](https://www.mux.com/) for [Payload CMS](https://payloadcms.com/) — a Videos collection that uploads directly to Mux, supports public or signed playback, exposes virtual playback/poster/gif URLs, and keeps Payload and Mux in sync.
4
+
5
+ ```bash
6
+ pnpm add @pro-laico/payload-mux
7
+ ```
8
+
9
+ **[Documentation →](https://payload-plugins.prolaico.com/docs/plugins/payload-mux)**
10
+
11
+ Based on [`@oversightstudio/mux-video`](https://github.com/oversightstudio/payload-plugins) (MIT) by [Idan Yekutiel](https://github.com/idanyekutiel).
@@ -0,0 +1,7 @@
1
+ import type Mux from '@mux/mux-node';
2
+ import type { CollectionConfig } from 'payload';
3
+ import type { MuxVideoPluginOptions } from '../types';
4
+ /** The Videos collection: an uploader field, identifying metadata Mux fills in, and a
5
+ * `playbackOptions` array whose virtual fields resolve to signed-or-public Mux URLs. */
6
+ export declare const MuxVideo: (mux: Mux, options: MuxVideoPluginOptions) => CollectionConfig;
7
+ //# sourceMappingURL=MuxVideo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MuxVideo.d.ts","sourceRoot":"","sources":["../../src/collections/MuxVideo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,eAAe,CAAA;AACpC,OAAO,KAAK,EAAE,gBAAgB,EAAS,MAAM,SAAS,CAAA;AAKtD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AA2DrD;yFACyF;AACzF,eAAO,MAAM,QAAQ,GAAI,KAAK,GAAG,EAAE,SAAS,qBAAqB,KAAG,gBAoHlE,CAAA"}
@@ -0,0 +1,266 @@
1
+ import { getAfterDeleteHook } from "../hooks/afterDelete.js";
2
+ import { getBeforeChangeHook } from "../hooks/beforeChange.js";
3
+ import { getBeforeValidateHook } from "../hooks/beforeValidate.js";
4
+ import { isAllowed } from "../lib/isAllowed.js";
5
+ const C = '@pro-laico/payload-mux/components';
6
+ /** Resolve the list-view Cell component for the configured `adminThumbnail` mode. */ const thumbnailCell = (mode)=>{
7
+ if (mode === 'image') return `${C}/MuxVideoImageCell#MuxVideoImageCell`;
8
+ if (mode === 'none') return undefined;
9
+ return `${C}/MuxVideoGifCell#MuxVideoGifCell`;
10
+ };
11
+ /** Sign a playback id for a given media `type` when the policy is `signed`, and append the
12
+ * token to `url`. A no-op for public playback. */ const signIfNeeded = async (mux, options, url, playbackId, policy, type, posterTimestamp)=>{
13
+ if (policy !== 'signed') return;
14
+ const params = typeof posterTimestamp === 'number' ? {
15
+ time: posterTimestamp.toString()
16
+ } : undefined;
17
+ const token = await mux.jwt.signPlaybackId(playbackId, {
18
+ expiration: options.signedUrlOptions?.expiration ?? '1d',
19
+ type,
20
+ params
21
+ });
22
+ url.searchParams.set('token', token);
23
+ };
24
+ /** Build one virtual, read-only URL field (`playbackUrl` / `posterUrl` / `gifUrl`). On read it
25
+ * builds the Mux URL from the sibling `playbackId`, stamps the poster `time` for thumbnail/gif,
26
+ * and signs it when the policy is `signed`. Returns null until a playback id exists. */ const signableUrlField = (mux, options, name, label, type, buildUrl)=>({
27
+ name,
28
+ label,
29
+ type: 'text',
30
+ virtual: true,
31
+ admin: {
32
+ hidden: true
33
+ },
34
+ hooks: {
35
+ afterRead: [
36
+ async ({ data, siblingData })=>{
37
+ const playbackId = siblingData?.playbackId;
38
+ if (!playbackId) return null;
39
+ const posterTimestamp = type !== 'video' && typeof data?.posterTimestamp === 'number' ? data.posterTimestamp : undefined;
40
+ const url = buildUrl(playbackId);
41
+ if (posterTimestamp !== undefined) url.searchParams.set('time', posterTimestamp.toString());
42
+ await signIfNeeded(mux, options, url, playbackId, siblingData.playbackPolicy, type, posterTimestamp);
43
+ return url.toString();
44
+ }
45
+ ]
46
+ }
47
+ });
48
+ /** The Videos collection: an uploader field, identifying metadata Mux fills in, and a
49
+ * `playbackOptions` array whose virtual fields resolve to signed-or-public Mux URLs. */ export const MuxVideo = (mux, options)=>({
50
+ slug: options.extendCollection ?? 'mux-video',
51
+ labels: {
52
+ singular: 'Video',
53
+ plural: 'Videos'
54
+ },
55
+ // Declares this as a `@pro-laico/payload-seed` asset collection: instead of uploading bytes,
56
+ // the seed engine hands a doc's `_file` to the `source` field below, whose beforeValidate hook
57
+ // uploads it to Mux. Plain config — payload-mux doesn't import the seed package.
58
+ custom: {
59
+ seedAsset: {
60
+ sourceField: 'source'
61
+ }
62
+ },
63
+ access: {
64
+ read: ({ req })=>isAllowed(options, req)
65
+ },
66
+ // Grouped with the other asset collections (images/icons/fonts) — but never re-grouping a
67
+ // collection the plugin merely extends.
68
+ admin: {
69
+ ...options.extendCollection ? {} : {
70
+ group: 'Assets'
71
+ },
72
+ useAsTitle: 'title',
73
+ defaultColumns: [
74
+ 'title',
75
+ 'muxUploader',
76
+ 'duration'
77
+ ]
78
+ },
79
+ hooks: {
80
+ afterDelete: [
81
+ getAfterDeleteHook(mux)
82
+ ],
83
+ beforeValidate: [
84
+ getBeforeValidateHook(mux, options)
85
+ ],
86
+ beforeChange: [
87
+ getBeforeChangeHook(mux)
88
+ ]
89
+ },
90
+ fields: [
91
+ {
92
+ name: 'muxUploader',
93
+ label: 'Video Preview',
94
+ type: 'ui',
95
+ admin: {
96
+ components: {
97
+ Field: `${C}/MuxUploaderField#MuxUploaderField`,
98
+ Cell: thumbnailCell(options.adminThumbnail)
99
+ }
100
+ }
101
+ },
102
+ // Transient server-side ingest input (a local path / URL, or `{ file|url, playbackPolicy,
103
+ // posterTimestamp }`). The beforeValidate hook uploads it to Mux and strips it — never
104
+ // persisted. Lets a video be created from a file without the admin's client-side upload.
105
+ {
106
+ name: 'source',
107
+ type: 'json',
108
+ admin: {
109
+ hidden: true,
110
+ disableListColumn: true,
111
+ disableBulkEdit: true
112
+ }
113
+ },
114
+ {
115
+ name: 'title',
116
+ type: 'text',
117
+ label: 'Title',
118
+ required: true,
119
+ unique: true,
120
+ admin: {
121
+ description: 'A unique title for this video that will help you identify it later.'
122
+ }
123
+ },
124
+ // Not `required`: it's filled by the beforeValidate (server-side ingest) / beforeChange
125
+ // (admin upload) hooks before the row is written. Marking it required would reject a
126
+ // create that supplies a `source` instead of an already-resolved `assetId`.
127
+ // Indexed: the webhook looks a doc up by assetId on every Mux event.
128
+ {
129
+ name: 'assetId',
130
+ type: 'text',
131
+ index: true,
132
+ admin: {
133
+ readOnly: true,
134
+ condition: (data)=>data.assetId
135
+ }
136
+ },
137
+ // Encoding lifecycle, hook-written: 'preparing' until Mux reports the asset ready (short
138
+ // poll or webhook), 'ready' once playable, 'errored' when Mux rejects it (see `error`).
139
+ // Not required — docs created before this field existed have no status.
140
+ {
141
+ name: 'status',
142
+ type: 'select',
143
+ options: [
144
+ {
145
+ label: 'Preparing',
146
+ value: 'preparing'
147
+ },
148
+ {
149
+ label: 'Ready',
150
+ value: 'ready'
151
+ },
152
+ {
153
+ label: 'Errored',
154
+ value: 'errored'
155
+ }
156
+ ],
157
+ admin: {
158
+ position: 'sidebar',
159
+ readOnly: true,
160
+ condition: (data)=>data.status
161
+ }
162
+ },
163
+ // Mux's error messages when `status` is 'errored'; surfaced by the uploader field.
164
+ {
165
+ name: 'error',
166
+ type: 'text',
167
+ admin: {
168
+ hidden: true,
169
+ readOnly: true
170
+ }
171
+ },
172
+ {
173
+ name: 'duration',
174
+ label: 'Duration',
175
+ type: 'number',
176
+ admin: {
177
+ readOnly: true,
178
+ condition: (data)=>data.duration
179
+ }
180
+ },
181
+ {
182
+ name: 'posterTimestamp',
183
+ type: 'number',
184
+ label: 'Poster Timestamp',
185
+ min: 0,
186
+ admin: {
187
+ description: 'A timestamp (in seconds) from the video to be used as the poster image. When unset, defaults to the middle of the video.',
188
+ condition: (data)=>data.duration,
189
+ step: 0.25
190
+ },
191
+ validate: (value, { siblingData })=>{
192
+ if (!siblingData.duration || !value) return true;
193
+ return siblingData.duration > value || 'Poster timestamp must be less than the video duration.';
194
+ }
195
+ },
196
+ {
197
+ name: 'aspectRatio',
198
+ label: 'Aspect Ratio',
199
+ type: 'text',
200
+ admin: {
201
+ readOnly: true,
202
+ condition: (data)=>data.aspectRatio
203
+ }
204
+ },
205
+ {
206
+ name: 'maxWidth',
207
+ type: 'number',
208
+ admin: {
209
+ readOnly: true,
210
+ condition: (data)=>data.maxWidth
211
+ }
212
+ },
213
+ {
214
+ name: 'maxHeight',
215
+ type: 'number',
216
+ admin: {
217
+ readOnly: true,
218
+ condition: (data)=>data.maxHeight
219
+ }
220
+ },
221
+ {
222
+ name: 'playbackOptions',
223
+ type: 'array',
224
+ admin: {
225
+ readOnly: true,
226
+ condition: (data)=>!!data.playbackOptions
227
+ },
228
+ fields: [
229
+ // Both required: the pair is always written together, and it tightens the generated types.
230
+ {
231
+ name: 'playbackId',
232
+ label: 'Playback ID',
233
+ type: 'text',
234
+ required: true,
235
+ admin: {
236
+ readOnly: true
237
+ }
238
+ },
239
+ {
240
+ name: 'playbackPolicy',
241
+ label: 'Playback Policy',
242
+ type: 'select',
243
+ required: true,
244
+ options: [
245
+ {
246
+ label: 'signed',
247
+ value: 'signed'
248
+ },
249
+ {
250
+ label: 'public',
251
+ value: 'public'
252
+ }
253
+ ],
254
+ admin: {
255
+ readOnly: true
256
+ }
257
+ },
258
+ signableUrlField(mux, options, 'playbackUrl', 'Playback URL', 'video', (id)=>new URL(`https://stream.mux.com/${id}.m3u8`)),
259
+ signableUrlField(mux, options, 'posterUrl', 'Poster URL', 'thumbnail', (id)=>new URL(`https://image.mux.com/${id}/thumbnail.${options.posterExtension ?? 'png'}`)),
260
+ signableUrlField(mux, options, 'gifUrl', 'Gif URL', 'gif', (id)=>new URL(`https://image.mux.com/${id}/animated.${options.animatedGifExtension ?? 'gif'}`))
261
+ ]
262
+ }
263
+ ]
264
+ });
265
+
266
+ //# sourceMappingURL=MuxVideo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/collections/MuxVideo.ts"],"sourcesContent":["import type Mux from '@mux/mux-node'\nimport type { CollectionConfig, Field } from 'payload'\nimport { getAfterDeleteHook } from '../hooks/afterDelete'\nimport { getBeforeChangeHook } from '../hooks/beforeChange'\nimport { getBeforeValidateHook } from '../hooks/beforeValidate'\nimport { isAllowed } from '../lib/isAllowed'\nimport type { MuxVideoPluginOptions } from '../types'\n\nconst C = '@pro-laico/payload-mux/components'\n\n/** Resolve the list-view Cell component for the configured `adminThumbnail` mode. */\nconst thumbnailCell = (mode: MuxVideoPluginOptions['adminThumbnail']): string | undefined => {\n if (mode === 'image') return `${C}/MuxVideoImageCell#MuxVideoImageCell`\n if (mode === 'none') return undefined\n return `${C}/MuxVideoGifCell#MuxVideoGifCell`\n}\n\n/** Sign a playback id for a given media `type` when the policy is `signed`, and append the\n * token to `url`. A no-op for public playback. */\nconst signIfNeeded = async (\n mux: Mux,\n options: MuxVideoPluginOptions,\n url: URL,\n playbackId: string,\n policy: unknown,\n type: 'video' | 'thumbnail' | 'gif',\n posterTimestamp?: number,\n): Promise<void> => {\n if (policy !== 'signed') return\n const params = typeof posterTimestamp === 'number' ? { time: posterTimestamp.toString() } : undefined\n const token = await mux.jwt.signPlaybackId(playbackId, { expiration: options.signedUrlOptions?.expiration ?? '1d', type, params })\n url.searchParams.set('token', token)\n}\n\n/** Build one virtual, read-only URL field (`playbackUrl` / `posterUrl` / `gifUrl`). On read it\n * builds the Mux URL from the sibling `playbackId`, stamps the poster `time` for thumbnail/gif,\n * and signs it when the policy is `signed`. Returns null until a playback id exists. */\nconst signableUrlField = (\n mux: Mux,\n options: MuxVideoPluginOptions,\n name: string,\n label: string,\n type: 'video' | 'thumbnail' | 'gif',\n buildUrl: (playbackId: string) => URL,\n): Field => ({\n name,\n label,\n type: 'text',\n virtual: true,\n admin: { hidden: true },\n hooks: {\n afterRead: [\n async ({ data, siblingData }) => {\n const playbackId = siblingData?.playbackId\n if (!playbackId) return null\n const posterTimestamp = type !== 'video' && typeof data?.posterTimestamp === 'number' ? data.posterTimestamp : undefined\n const url = buildUrl(playbackId)\n if (posterTimestamp !== undefined) url.searchParams.set('time', posterTimestamp.toString())\n await signIfNeeded(mux, options, url, playbackId, siblingData.playbackPolicy, type, posterTimestamp)\n return url.toString()\n },\n ],\n },\n})\n\n/** The Videos collection: an uploader field, identifying metadata Mux fills in, and a\n * `playbackOptions` array whose virtual fields resolve to signed-or-public Mux URLs. */\nexport const MuxVideo = (mux: Mux, options: MuxVideoPluginOptions): CollectionConfig => ({\n slug: (options.extendCollection as string) ?? 'mux-video',\n labels: { singular: 'Video', plural: 'Videos' },\n // Declares this as a `@pro-laico/payload-seed` asset collection: instead of uploading bytes,\n // the seed engine hands a doc's `_file` to the `source` field below, whose beforeValidate hook\n // uploads it to Mux. Plain config — payload-mux doesn't import the seed package.\n custom: { seedAsset: { sourceField: 'source' } },\n access: { read: ({ req }) => isAllowed(options, req) },\n // Grouped with the other asset collections (images/icons/fonts) — but never re-grouping a\n // collection the plugin merely extends.\n admin: {\n ...(options.extendCollection ? {} : { group: 'Assets' }),\n useAsTitle: 'title',\n defaultColumns: ['title', 'muxUploader', 'duration'],\n },\n hooks: {\n afterDelete: [getAfterDeleteHook(mux)],\n beforeValidate: [getBeforeValidateHook(mux, options)],\n beforeChange: [getBeforeChangeHook(mux)],\n },\n fields: [\n {\n name: 'muxUploader',\n label: 'Video Preview',\n type: 'ui',\n admin: { components: { Field: `${C}/MuxUploaderField#MuxUploaderField`, Cell: thumbnailCell(options.adminThumbnail) } },\n },\n // Transient server-side ingest input (a local path / URL, or `{ file|url, playbackPolicy,\n // posterTimestamp }`). The beforeValidate hook uploads it to Mux and strips it — never\n // persisted. Lets a video be created from a file without the admin's client-side upload.\n { name: 'source', type: 'json', admin: { hidden: true, disableListColumn: true, disableBulkEdit: true } },\n {\n name: 'title',\n type: 'text',\n label: 'Title',\n required: true,\n unique: true,\n admin: { description: 'A unique title for this video that will help you identify it later.' },\n },\n // Not `required`: it's filled by the beforeValidate (server-side ingest) / beforeChange\n // (admin upload) hooks before the row is written. Marking it required would reject a\n // create that supplies a `source` instead of an already-resolved `assetId`.\n // Indexed: the webhook looks a doc up by assetId on every Mux event.\n { name: 'assetId', type: 'text', index: true, admin: { readOnly: true, condition: (data) => data.assetId } },\n // Encoding lifecycle, hook-written: 'preparing' until Mux reports the asset ready (short\n // poll or webhook), 'ready' once playable, 'errored' when Mux rejects it (see `error`).\n // Not required — docs created before this field existed have no status.\n {\n name: 'status',\n type: 'select',\n options: [\n { label: 'Preparing', value: 'preparing' },\n { label: 'Ready', value: 'ready' },\n { label: 'Errored', value: 'errored' },\n ],\n admin: { position: 'sidebar', readOnly: true, condition: (data) => data.status },\n },\n // Mux's error messages when `status` is 'errored'; surfaced by the uploader field.\n { name: 'error', type: 'text', admin: { hidden: true, readOnly: true } },\n { name: 'duration', label: 'Duration', type: 'number', admin: { readOnly: true, condition: (data) => data.duration } },\n {\n name: 'posterTimestamp',\n type: 'number',\n label: 'Poster Timestamp',\n min: 0,\n admin: {\n description: 'A timestamp (in seconds) from the video to be used as the poster image. When unset, defaults to the middle of the video.',\n condition: (data) => data.duration,\n step: 0.25,\n },\n validate: (value: number | null | undefined, { siblingData }: { siblingData: Partial<{ duration: number }> }) => {\n if (!siblingData.duration || !value) return true\n return siblingData.duration > value || 'Poster timestamp must be less than the video duration.'\n },\n },\n { name: 'aspectRatio', label: 'Aspect Ratio', type: 'text', admin: { readOnly: true, condition: (data) => data.aspectRatio } },\n { name: 'maxWidth', type: 'number', admin: { readOnly: true, condition: (data) => data.maxWidth } },\n { name: 'maxHeight', type: 'number', admin: { readOnly: true, condition: (data) => data.maxHeight } },\n {\n name: 'playbackOptions',\n type: 'array',\n admin: { readOnly: true, condition: (data) => !!data.playbackOptions },\n fields: [\n // Both required: the pair is always written together, and it tightens the generated types.\n { name: 'playbackId', label: 'Playback ID', type: 'text', required: true, admin: { readOnly: true } },\n {\n name: 'playbackPolicy',\n label: 'Playback Policy',\n type: 'select',\n required: true,\n options: [\n { label: 'signed', value: 'signed' },\n { label: 'public', value: 'public' },\n ],\n admin: { readOnly: true },\n },\n signableUrlField(mux, options, 'playbackUrl', 'Playback URL', 'video', (id) => new URL(`https://stream.mux.com/${id}.m3u8`)),\n signableUrlField(\n mux,\n options,\n 'posterUrl',\n 'Poster URL',\n 'thumbnail',\n (id) => new URL(`https://image.mux.com/${id}/thumbnail.${options.posterExtension ?? 'png'}`),\n ),\n signableUrlField(\n mux,\n options,\n 'gifUrl',\n 'Gif URL',\n 'gif',\n (id) => new URL(`https://image.mux.com/${id}/animated.${options.animatedGifExtension ?? 'gif'}`),\n ),\n ],\n },\n ],\n})\n"],"names":["getAfterDeleteHook","getBeforeChangeHook","getBeforeValidateHook","isAllowed","C","thumbnailCell","mode","undefined","signIfNeeded","mux","options","url","playbackId","policy","type","posterTimestamp","params","time","toString","token","jwt","signPlaybackId","expiration","signedUrlOptions","searchParams","set","signableUrlField","name","label","buildUrl","virtual","admin","hidden","hooks","afterRead","data","siblingData","playbackPolicy","MuxVideo","slug","extendCollection","labels","singular","plural","custom","seedAsset","sourceField","access","read","req","group","useAsTitle","defaultColumns","afterDelete","beforeValidate","beforeChange","fields","components","Field","Cell","adminThumbnail","disableListColumn","disableBulkEdit","required","unique","description","index","readOnly","condition","assetId","value","position","status","duration","min","step","validate","aspectRatio","maxWidth","maxHeight","playbackOptions","id","URL","posterExtension","animatedGifExtension"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import './mux-uploader.css';
2
+ /**
3
+ * The admin Field for the `muxUploader` UI field. Four states: an uploader before a video
4
+ * exists, a "processing" notice while Mux encodes, an error notice when Mux rejected the
5
+ * upload (`status: 'errored'`), and the Mux player once a playback URL is available. Uploads
6
+ * go directly to Mux via a direct-upload URL the plugin's endpoint mints.
7
+ */
8
+ export declare const MuxUploaderField: () => import("react").JSX.Element;
9
+ export default MuxUploaderField;
10
+ //# sourceMappingURL=MuxUploaderField.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MuxUploaderField.d.ts","sourceRoot":"","sources":["../../src/components/MuxUploaderField.tsx"],"names":[],"mappings":"AAMA,OAAO,oBAAoB,CAAA;AAK3B;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,mCAyF5B,CAAA;AAED,eAAe,gBAAgB,CAAA"}
@@ -0,0 +1,143 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import MuxPlayer from "@mux/mux-player-react";
4
+ import MuxUploader from "@mux/mux-uploader-react";
5
+ import { toast, useConfig, useForm, useFormFields } from "@payloadcms/ui";
6
+ import { useCallback, useEffect, useRef, useState } from "react";
7
+ import "./mux-uploader.css";
8
+ /** Strip the extension from a filename (`clip.final.mp4` → `clip.final`). */ const stripExtension = (name)=>name.replace(/\.[^./\\]+$/, '');
9
+ /**
10
+ * The admin Field for the `muxUploader` UI field. Four states: an uploader before a video
11
+ * exists, a "processing" notice while Mux encodes, an error notice when Mux rejected the
12
+ * upload (`status: 'errored'`), and the Mux player once a playback URL is available. Uploads
13
+ * go directly to Mux via a direct-upload URL the plugin's endpoint mints.
14
+ */ export const MuxUploaderField = ()=>{
15
+ const { config } = useConfig();
16
+ const apiUrl = config.routes.api;
17
+ const [uploadId, setUploadId] = useState('');
18
+ const { assetId, setAssetId, title, setTitle, setFile, playbackUrl, status, error } = useFormFields(([fields, dispatch])=>({
19
+ assetId: fields.assetId,
20
+ setAssetId: (value)=>dispatch({
21
+ type: 'UPDATE',
22
+ path: 'assetId',
23
+ value
24
+ }),
25
+ title: fields.title,
26
+ setTitle: (value)=>dispatch({
27
+ type: 'UPDATE',
28
+ path: 'title',
29
+ value
30
+ }),
31
+ setFile: (value)=>dispatch({
32
+ type: 'UPDATE',
33
+ path: 'file',
34
+ value
35
+ }),
36
+ playbackUrl: fields['playbackOptions.0.playbackUrl']?.value,
37
+ status: fields.status?.value,
38
+ error: fields.error?.value
39
+ }));
40
+ const { submit, setProcessing } = useForm();
41
+ const containerRef = useRef(null);
42
+ const getUploadUrl = useCallback(async ()=>{
43
+ const response = await fetch(`${apiUrl}/mux/upload`, {
44
+ method: 'POST'
45
+ });
46
+ if (!response.ok) {
47
+ const body = (await response.text()).slice(0, 200);
48
+ toast.error(`Could not create a Mux upload (${response.status}): ${body}`);
49
+ throw new Error(`[payload-mux] upload URL request failed (${response.status}): ${body}`);
50
+ }
51
+ const { id, url } = await response.json();
52
+ setUploadId(id);
53
+ return url;
54
+ }, [
55
+ apiUrl
56
+ ]);
57
+ const onUploadStart = useCallback(({ detail: { file } })=>{
58
+ const resolvedTitle = title?.value || stripExtension(file.name);
59
+ if (!title?.value) setTitle(resolvedTitle);
60
+ setFile(new File([], resolvedTitle, {
61
+ type: file.type,
62
+ lastModified: file.lastModified
63
+ }));
64
+ }, [
65
+ title?.value,
66
+ setTitle,
67
+ setFile
68
+ ]);
69
+ const onSuccess = useCallback(async ()=>{
70
+ setProcessing(true);
71
+ const fetchUpload = async ()=>(await fetch(`${apiUrl}/mux/upload?id=${uploadId}`)).json();
72
+ let upload = await fetchUpload();
73
+ // The asset_id may lag the upload by a moment; poll until Mux assigns it — but bound it so a
74
+ // stuck/errored upload can't hang the form on "processing" forever.
75
+ const deadline = Date.now() + 60_000;
76
+ while(!upload.asset_id && Date.now() < deadline){
77
+ await new Promise((resolve)=>setTimeout(resolve, 1000));
78
+ upload = await fetchUpload();
79
+ }
80
+ if (!upload.asset_id) {
81
+ setProcessing(false);
82
+ toast.error('The upload did not produce a Mux asset id within 60 seconds — please try again.');
83
+ return;
84
+ }
85
+ const { asset_id } = upload;
86
+ setAssetId(asset_id);
87
+ // Defer the submit so the assetId dispatch is flushed first.
88
+ setTimeout(()=>submit({
89
+ overrides: {
90
+ assetId: asset_id
91
+ }
92
+ }), 0);
93
+ }, [
94
+ apiUrl,
95
+ uploadId,
96
+ setAssetId,
97
+ setProcessing,
98
+ submit
99
+ ]);
100
+ // Hide Payload's default `.file-field` (present only when extending an upload collection),
101
+ // scoped to this field's own form so it can't hide a file field elsewhere on the page.
102
+ useEffect(()=>{
103
+ const fileField = containerRef.current?.closest('form')?.querySelector('.file-field');
104
+ if (fileField) fileField.style.display = 'none';
105
+ }, []);
106
+ return /*#__PURE__*/ _jsxs("div", {
107
+ className: "mux-uploader",
108
+ ref: containerRef,
109
+ children: [
110
+ !assetId?.value && /*#__PURE__*/ _jsx(MuxUploader, {
111
+ endpoint: getUploadUrl,
112
+ onUploadStart: onUploadStart,
113
+ onSuccess: onSuccess
114
+ }),
115
+ Boolean(assetId?.value) && status === 'errored' && /*#__PURE__*/ _jsxs("div", {
116
+ className: "mux-uploader__error",
117
+ children: [
118
+ "Mux could not process this video",
119
+ error ? `: ${error}` : '',
120
+ ". Delete this video and upload it again."
121
+ ]
122
+ }),
123
+ Boolean(assetId?.value) && !playbackUrl && status !== 'errored' && /*#__PURE__*/ _jsxs("div", {
124
+ className: "mux-uploader__processing",
125
+ children: [
126
+ "Video is being encoded. This typically takes less than 90 seconds, please refresh the page in a moment. If this persists, ensure your Mux webhook points at ",
127
+ `${apiUrl}/mux/webhook`,
128
+ " (see docs)"
129
+ ]
130
+ }),
131
+ playbackUrl && /*#__PURE__*/ _jsx(MuxPlayer, {
132
+ src: playbackUrl,
133
+ streamType: "on-demand",
134
+ style: {
135
+ height: '60vh'
136
+ }
137
+ })
138
+ ]
139
+ });
140
+ };
141
+ export default MuxUploaderField;
142
+
143
+ //# sourceMappingURL=MuxUploaderField.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/MuxUploaderField.tsx"],"sourcesContent":["'use client'\n\nimport MuxPlayer from '@mux/mux-player-react'\nimport MuxUploader from '@mux/mux-uploader-react'\nimport { toast, useConfig, useForm, useFormFields } from '@payloadcms/ui'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport './mux-uploader.css'\n\n/** Strip the extension from a filename (`clip.final.mp4` → `clip.final`). */\nconst stripExtension = (name: string): string => name.replace(/\\.[^./\\\\]+$/, '')\n\n/**\n * The admin Field for the `muxUploader` UI field. Four states: an uploader before a video\n * exists, a \"processing\" notice while Mux encodes, an error notice when Mux rejected the\n * upload (`status: 'errored'`), and the Mux player once a playback URL is available. Uploads\n * go directly to Mux via a direct-upload URL the plugin's endpoint mints.\n */\nexport const MuxUploaderField = () => {\n const { config } = useConfig()\n const apiUrl = config.routes.api\n\n const [uploadId, setUploadId] = useState('')\n const { assetId, setAssetId, title, setTitle, setFile, playbackUrl, status, error } = useFormFields(([fields, dispatch]) => ({\n assetId: fields.assetId,\n setAssetId: (value: string) => dispatch({ type: 'UPDATE', path: 'assetId', value }),\n title: fields.title,\n setTitle: (value: string) => dispatch({ type: 'UPDATE', path: 'title', value }),\n setFile: (value: File) => dispatch({ type: 'UPDATE', path: 'file', value }),\n playbackUrl: fields['playbackOptions.0.playbackUrl']?.value as string | undefined,\n status: fields.status?.value as string | undefined,\n error: fields.error?.value as string | undefined,\n }))\n\n const { submit, setProcessing } = useForm()\n const containerRef = useRef<HTMLDivElement>(null)\n\n const getUploadUrl = useCallback(async () => {\n const response = await fetch(`${apiUrl}/mux/upload`, { method: 'POST' })\n if (!response.ok) {\n const body = (await response.text()).slice(0, 200)\n toast.error(`Could not create a Mux upload (${response.status}): ${body}`)\n throw new Error(`[payload-mux] upload URL request failed (${response.status}): ${body}`)\n }\n const { id, url } = (await response.json()) as { id: string; url: string }\n setUploadId(id)\n return url\n }, [apiUrl])\n\n const onUploadStart = useCallback(\n ({ detail: { file } }: { detail: { file: File } }) => {\n const resolvedTitle = (title?.value as string) || stripExtension(file.name)\n if (!title?.value) setTitle(resolvedTitle)\n setFile(new File([], resolvedTitle, { type: file.type, lastModified: file.lastModified }))\n },\n [title?.value, setTitle, setFile],\n )\n\n const onSuccess = useCallback(async () => {\n setProcessing(true)\n\n const fetchUpload = async () => (await fetch(`${apiUrl}/mux/upload?id=${uploadId}`)).json()\n let upload = await fetchUpload()\n // The asset_id may lag the upload by a moment; poll until Mux assigns it — but bound it so a\n // stuck/errored upload can't hang the form on \"processing\" forever.\n const deadline = Date.now() + 60_000\n while (!upload.asset_id && Date.now() < deadline) {\n await new Promise((resolve) => setTimeout(resolve, 1000))\n upload = await fetchUpload()\n }\n\n if (!upload.asset_id) {\n setProcessing(false)\n toast.error('The upload did not produce a Mux asset id within 60 seconds — please try again.')\n return\n }\n\n const { asset_id } = upload\n setAssetId(asset_id)\n // Defer the submit so the assetId dispatch is flushed first.\n setTimeout(() => submit({ overrides: { assetId: asset_id } }), 0)\n }, [apiUrl, uploadId, setAssetId, setProcessing, submit])\n\n // Hide Payload's default `.file-field` (present only when extending an upload collection),\n // scoped to this field's own form so it can't hide a file field elsewhere on the page.\n useEffect(() => {\n const fileField = containerRef.current?.closest('form')?.querySelector('.file-field') as HTMLElement | null\n if (fileField) fileField.style.display = 'none'\n }, [])\n\n return (\n <div className=\"mux-uploader\" ref={containerRef}>\n {!assetId?.value && <MuxUploader endpoint={getUploadUrl} onUploadStart={onUploadStart} onSuccess={onSuccess} />}\n {Boolean(assetId?.value) && status === 'errored' && (\n <div className=\"mux-uploader__error\">\n Mux could not process this video{error ? `: ${error}` : ''}. Delete this video and upload it again.\n </div>\n )}\n {Boolean(assetId?.value) && !playbackUrl && status !== 'errored' && (\n <div className=\"mux-uploader__processing\">\n Video is being encoded. This typically takes less than 90 seconds, please refresh the page in a moment. If this persists, ensure your\n Mux webhook points at {`${apiUrl}/mux/webhook`} (see docs)\n </div>\n )}\n {playbackUrl && <MuxPlayer src={playbackUrl} streamType=\"on-demand\" style={{ height: '60vh' }} />}\n </div>\n )\n}\n\nexport default MuxUploaderField\n"],"names":["MuxPlayer","MuxUploader","toast","useConfig","useForm","useFormFields","useCallback","useEffect","useRef","useState","stripExtension","name","replace","MuxUploaderField","config","apiUrl","routes","api","uploadId","setUploadId","assetId","setAssetId","title","setTitle","setFile","playbackUrl","status","error","fields","dispatch","value","type","path","submit","setProcessing","containerRef","getUploadUrl","response","fetch","method","ok","body","text","slice","Error","id","url","json","onUploadStart","detail","file","resolvedTitle","File","lastModified","onSuccess","fetchUpload","upload","deadline","Date","now","asset_id","Promise","resolve","setTimeout","overrides","fileField","current","closest","querySelector","style","display","div","className","ref","endpoint","Boolean","src","streamType","height"],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { DefaultCellComponentProps } from 'payload';
2
+ /** List-view Cell showing an animated Mux gif preview (the `adminThumbnail: 'gif'` mode). */
3
+ export declare const MuxVideoGifCell: ({ rowData }: DefaultCellComponentProps) => import("react").JSX.Element;
4
+ export default MuxVideoGifCell;
5
+ //# sourceMappingURL=MuxVideoGifCell.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MuxVideoGifCell.d.ts","sourceRoot":"","sources":["../../src/components/MuxVideoGifCell.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAA;AAExD,6FAA6F;AAC7F,eAAO,MAAM,eAAe,GAAI,aAAa,yBAAyB,gCAMrE,CAAA;AAED,eAAe,eAAe,CAAA"}
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /** List-view Cell showing an animated Mux gif preview (the `adminThumbnail: 'gif'` mode). */ export const MuxVideoGifCell = ({ rowData })=>{
3
+ const playbackOption = rowData?.playbackOptions?.[0];
4
+ if (!playbackOption) return /*#__PURE__*/ _jsx(_Fragment, {
5
+ children: "Preview not available."
6
+ });
7
+ // biome-ignore lint/performance/noImgElement: admin list-view thumbnail (Mux image URL), not a Next page
8
+ return /*#__PURE__*/ _jsx("img", {
9
+ style: {
10
+ width: 80,
11
+ height: 80,
12
+ objectFit: 'cover'
13
+ },
14
+ loading: "lazy",
15
+ alt: rowData?.title,
16
+ src: playbackOption.gifUrl
17
+ });
18
+ };
19
+ export default MuxVideoGifCell;
20
+
21
+ //# sourceMappingURL=MuxVideoGifCell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/MuxVideoGifCell.tsx"],"sourcesContent":["import type { DefaultCellComponentProps } from 'payload'\n\n/** List-view Cell showing an animated Mux gif preview (the `adminThumbnail: 'gif'` mode). */\nexport const MuxVideoGifCell = ({ rowData }: DefaultCellComponentProps) => {\n const playbackOption = rowData?.playbackOptions?.[0]\n if (!playbackOption) return <>Preview not available.</>\n\n // biome-ignore lint/performance/noImgElement: admin list-view thumbnail (Mux image URL), not a Next page\n return <img style={{ width: 80, height: 80, objectFit: 'cover' }} loading=\"lazy\" alt={rowData?.title} src={playbackOption.gifUrl} />\n}\n\nexport default MuxVideoGifCell\n"],"names":["MuxVideoGifCell","rowData","playbackOption","playbackOptions","img","style","width","height","objectFit","loading","alt","title","src","gifUrl"],"mappings":";AAEA,2FAA2F,GAC3F,OAAO,MAAMA,kBAAkB,CAAC,EAAEC,OAAO,EAA6B;IACpE,MAAMC,iBAAiBD,SAASE,iBAAiB,CAAC,EAAE;IACpD,IAAI,CAACD,gBAAgB,qBAAO;kBAAE;;IAE9B,yGAAyG;IACzG,qBAAO,KAACE;QAAIC,OAAO;YAAEC,OAAO;YAAIC,QAAQ;YAAIC,WAAW;QAAQ;QAAGC,SAAQ;QAAOC,KAAKT,SAASU;QAAOC,KAAKV,eAAeW,MAAM;;AAClI,EAAC;AAED,eAAeb,gBAAe"}
@@ -0,0 +1,5 @@
1
+ import type { DefaultCellComponentProps } from 'payload';
2
+ /** List-view Cell showing a static Mux poster image (the `adminThumbnail: 'image'` mode). */
3
+ export declare const MuxVideoImageCell: ({ rowData }: DefaultCellComponentProps) => import("react").JSX.Element;
4
+ export default MuxVideoImageCell;
5
+ //# sourceMappingURL=MuxVideoImageCell.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MuxVideoImageCell.d.ts","sourceRoot":"","sources":["../../src/components/MuxVideoImageCell.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAA;AAExD,6FAA6F;AAC7F,eAAO,MAAM,iBAAiB,GAAI,aAAa,yBAAyB,gCAMvE,CAAA;AAED,eAAe,iBAAiB,CAAA"}
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /** List-view Cell showing a static Mux poster image (the `adminThumbnail: 'image'` mode). */ export const MuxVideoImageCell = ({ rowData })=>{
3
+ const playbackOption = rowData?.playbackOptions?.[0];
4
+ if (!playbackOption) return /*#__PURE__*/ _jsx(_Fragment, {
5
+ children: "Preview not available."
6
+ });
7
+ // biome-ignore lint/performance/noImgElement: admin list-view thumbnail (Mux image URL), not a Next page
8
+ return /*#__PURE__*/ _jsx("img", {
9
+ style: {
10
+ width: 80,
11
+ height: 80,
12
+ objectFit: 'cover'
13
+ },
14
+ loading: "lazy",
15
+ alt: rowData?.title,
16
+ src: playbackOption.posterUrl
17
+ });
18
+ };
19
+ export default MuxVideoImageCell;
20
+
21
+ //# sourceMappingURL=MuxVideoImageCell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/MuxVideoImageCell.tsx"],"sourcesContent":["import type { DefaultCellComponentProps } from 'payload'\n\n/** List-view Cell showing a static Mux poster image (the `adminThumbnail: 'image'` mode). */\nexport const MuxVideoImageCell = ({ rowData }: DefaultCellComponentProps) => {\n const playbackOption = rowData?.playbackOptions?.[0]\n if (!playbackOption) return <>Preview not available.</>\n\n // biome-ignore lint/performance/noImgElement: admin list-view thumbnail (Mux image URL), not a Next page\n return <img style={{ width: 80, height: 80, objectFit: 'cover' }} loading=\"lazy\" alt={rowData?.title} src={playbackOption.posterUrl} />\n}\n\nexport default MuxVideoImageCell\n"],"names":["MuxVideoImageCell","rowData","playbackOption","playbackOptions","img","style","width","height","objectFit","loading","alt","title","src","posterUrl"],"mappings":";AAEA,2FAA2F,GAC3F,OAAO,MAAMA,oBAAoB,CAAC,EAAEC,OAAO,EAA6B;IACtE,MAAMC,iBAAiBD,SAASE,iBAAiB,CAAC,EAAE;IACpD,IAAI,CAACD,gBAAgB,qBAAO;kBAAE;;IAE9B,yGAAyG;IACzG,qBAAO,KAACE;QAAIC,OAAO;YAAEC,OAAO;YAAIC,QAAQ;YAAIC,WAAW;QAAQ;QAAGC,SAAQ;QAAOC,KAAKT,SAASU;QAAOC,KAAKV,eAAeW,SAAS;;AACrI,EAAC;AAED,eAAeb,kBAAiB"}