sanity-plugin-mux-input 2.4.0 → 2.5.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/LICENSE +1 -1
- package/README.md +57 -55
- package/dist/index.js +223 -88
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +227 -92
- package/dist/index.mjs.map +1 -1
- package/package.json +13 -17
- package/src/components/EditThumbnailDialog.tsx +122 -0
- package/src/components/PlayerActionsMenu.tsx +13 -0
- package/src/components/Uploader.tsx +21 -15
- package/src/components/VideoPlayer.tsx +66 -49
- package/src/components/VideoThumbnail.tsx +15 -8
- package/src/context/DialogStateContext.tsx +36 -0
- package/src/hooks/useAssets.ts +29 -23
- package/src/util/createUrlParamsObject.ts +25 -0
- package/src/util/formatSeconds.ts +28 -1
- package/src/util/getAnimatedPosterSrc.ts +5 -13
- package/src/util/getPosterSrc.ts +10 -15
- package/src/util/getVideoMetadata.ts +1 -1
- package/src/util/types.ts +4 -0
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -77,19 +77,19 @@ The Mux plugin will find its access tokens by fetching this document.
|
|
|
77
77
|
|
|
78
78
|
When a Mux video is uploaded/chosen in a document via this plugin, it gets stored as a reference to the video document:
|
|
79
79
|
|
|
80
|
-
```
|
|
80
|
+
```json5
|
|
81
81
|
// example document
|
|
82
82
|
{
|
|
83
|
-
|
|
83
|
+
_type: 'exampleSchemaWithVideo',
|
|
84
84
|
// Example video field
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
}
|
|
85
|
+
myVideoField: {
|
|
86
|
+
_type: 'mux.video',
|
|
87
|
+
asset: {
|
|
88
|
+
_type: 'reference',
|
|
89
|
+
_weak: true,
|
|
90
|
+
_ref: '4e37284e-cec2-406d-973c-fdf9ab1e5598', // 👈 ID of the document holding the video's Mux data
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
93
|
}
|
|
94
94
|
```
|
|
95
95
|
|
|
@@ -112,54 +112,55 @@ Before you can display videos in your frontend, you need to follow these referen
|
|
|
112
112
|
|
|
113
113
|
For reference, here's an example `mux.videoAsset` document:
|
|
114
114
|
|
|
115
|
-
```
|
|
115
|
+
```json5
|
|
116
116
|
{
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
117
|
+
_id: '4e37284e-cec2-406d-973c-fdf9ab1e5598',
|
|
118
|
+
_type: 'mux.videoAsset',
|
|
119
|
+
assetId: '7ovyI76F92n02H00mWP7lOCZMIU00N4iysDiQDNppX026HY',
|
|
120
|
+
filename: 'mux-example-video.mp4',
|
|
121
|
+
status: 'ready',
|
|
122
|
+
playbackId: 'YA02HBpY02fKWHDRMNilo301pdH02LY3k9HTcK43ItGJLWA',
|
|
123
|
+
thumbTime: 65.82,
|
|
123
124
|
// Full Mux asset data:
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
125
|
+
data: {
|
|
126
|
+
encoding_tier: 'smart',
|
|
127
|
+
max_resolution_tier: '1080p',
|
|
128
|
+
aspect_ratio: '16:9',
|
|
129
|
+
created_at: '1706645034',
|
|
130
|
+
duration: 25.492133,
|
|
131
|
+
status: 'ready',
|
|
132
|
+
master_access: 'none',
|
|
133
|
+
max_stored_frame_rate: 29.97,
|
|
134
|
+
playback_ids: [
|
|
134
135
|
{
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
136
|
+
id: 'YA02HBpY02fKWHDRMNilo301pdH02LY3k9HTcK43ItGJLWA',
|
|
137
|
+
policy: 'signed',
|
|
138
|
+
},
|
|
138
139
|
],
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
resolution_tier: '1080p',
|
|
141
|
+
ingest_type: 'on_demand_url',
|
|
142
|
+
max_stored_resolution: 'HD',
|
|
143
|
+
tracks: [
|
|
143
144
|
{
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
145
|
+
max_channel_layout: 'stereo',
|
|
146
|
+
max_channels: 2,
|
|
147
|
+
id: '00MKMC73SYimw1YTh0102lPJJp9w2R5rHddpNX1N9opAMk',
|
|
148
|
+
type: 'audio',
|
|
149
|
+
primary: true,
|
|
150
|
+
duration: 25.45,
|
|
150
151
|
},
|
|
151
152
|
{
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
153
|
+
max_frame_rate: 29.97,
|
|
154
|
+
max_height: 1080,
|
|
155
|
+
id: 'g1wEph3CVvbJL01YNKzAWMyH8N1SxW00WeECGjqwEHW9g',
|
|
156
|
+
type: 'video',
|
|
157
|
+
duration: 25.4254,
|
|
158
|
+
max_width: 1920,
|
|
159
|
+
},
|
|
159
160
|
],
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
161
|
+
id: '7ovyI76F92n02H00mWP7lOCZMIU00N4iysDiQDNppX026HY',
|
|
162
|
+
mp4_support: 'none',
|
|
163
|
+
},
|
|
163
164
|
}
|
|
164
165
|
```
|
|
165
166
|
|
|
@@ -279,7 +280,7 @@ Issues are actively monitored and PRs are welcome. When developing this plugin t
|
|
|
279
280
|
|
|
280
281
|
### Publishing
|
|
281
282
|
|
|
282
|
-
|
|
283
|
+
You can run the ["CI and Release" workflow](<[https://github.com/sanity-io/sanity-plugin-mux-input/actions/workflows/ci.yml](https://github.com/sanity-io/sanity-plugin-mux-input/actions/workflows/main.yml)>).
|
|
283
284
|
Make sure to select the main branch and check "Release new version".
|
|
284
285
|
|
|
285
286
|
Semantic release will only release on configured branches, so it is safe to run release on any branch.
|
|
@@ -301,11 +302,12 @@ After Studio v3 turns stable this behavior will change. The v2 version will then
|
|
|
301
302
|
|
|
302
303
|
### Develop & test
|
|
303
304
|
|
|
304
|
-
|
|
305
|
-
with default configuration for build & watch scripts.
|
|
305
|
+
You can run the example locally by doing the following:
|
|
306
306
|
|
|
307
|
-
|
|
308
|
-
|
|
307
|
+
1. run `npm install` and `npm dev` on the root of the repo
|
|
308
|
+
2. In the terminal, a command with `yalc` will be shown, that command will allow you to run the version that you have locally directly on the example or on your own app.
|
|
309
|
+
3. run `npm install` and `npm dev` on the `/example` directory where the app with the example exists or in your own app
|
|
310
|
+
4. the studio and app should auto reload with your changes in the plugin package you have locally
|
|
309
311
|
|
|
310
312
|
### Release new version
|
|
311
313
|
|
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
19
19
|
mod
|
|
20
20
|
));
|
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: !0 });
|
|
22
|
-
var sanity = require("sanity"), jsxRuntime = require("react/jsx-runtime"), icons = require("@sanity/icons"), ui = require("@sanity/ui"), React = require("react"), compact = require("lodash/compact.js"), toLower = require("lodash/toLower.js"), trim = require("lodash/trim.js"), uniq = require("lodash/uniq.js"), words = require("lodash/words.js"), styledComponents = require("styled-components"), uuid = require("@sanity/uuid"), rxjs = require("rxjs"), operators = require("rxjs/operators"), suspendReact = require("suspend-react"), MuxPlayer = require("@mux/mux-player-react"), desk = require("sanity/desk"), router = require("sanity/router"), isNumber = require("lodash/isNumber.js"), isString = require("lodash/isString.js"),
|
|
22
|
+
var sanity = require("sanity"), jsxRuntime = require("react/jsx-runtime"), icons = require("@sanity/icons"), ui = require("@sanity/ui"), React = require("react"), reactRx = require("react-rx"), compact = require("lodash/compact.js"), toLower = require("lodash/toLower.js"), trim = require("lodash/trim.js"), uniq = require("lodash/uniq.js"), words = require("lodash/words.js"), styledComponents = require("styled-components"), uuid = require("@sanity/uuid"), rxjs = require("rxjs"), operators = require("rxjs/operators"), suspendReact = require("suspend-react"), MuxPlayer = require("@mux/mux-player-react"), desk = require("sanity/desk"), router = require("sanity/router"), isNumber = require("lodash/isNumber.js"), isString = require("lodash/isString.js"), useSWR = require("swr"), scrollIntoView = require("scroll-into-view-if-needed"), upchunk = require("@mux/upchunk"), reactIs = require("react-is"), LanguagesList = require("iso-639-1");
|
|
23
23
|
function _interopDefaultCompat(e) {
|
|
24
24
|
return e && typeof e == "object" && "default" in e ? e : { default: e };
|
|
25
25
|
}
|
|
@@ -71,31 +71,38 @@ const ASSET_SORT_OPTIONS = {
|
|
|
71
71
|
createdAsc: { groq: "_createdAt asc", label: "First created (oldest)" },
|
|
72
72
|
filenameAsc: { groq: "filename asc", label: "By filename (A-Z)" },
|
|
73
73
|
filenameDesc: { groq: "filename desc", label: "By filename (Z-A)" }
|
|
74
|
-
}, useAssetDocuments =
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
74
|
+
}, useAssetDocuments = ({
|
|
75
|
+
documentStore,
|
|
76
|
+
sort,
|
|
77
|
+
searchQuery
|
|
78
|
+
}) => {
|
|
79
|
+
const memoizedObservable = React.useMemo(() => {
|
|
80
|
+
const search = createSearchFilter(searchQuery), filter = ['_type == "mux.videoAsset"', ...search.filter].filter(Boolean).join(" && "), sortFragment = ASSET_SORT_OPTIONS[sort].groq;
|
|
81
|
+
return documentStore.listenQuery(
|
|
82
|
+
/* groq */
|
|
83
|
+
`*[${filter}] | order(${sortFragment})`,
|
|
84
|
+
search.params,
|
|
85
|
+
{
|
|
86
|
+
apiVersion: SANITY_API_VERSION
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
}, [documentStore, sort, searchQuery]);
|
|
90
|
+
return reactRx.useObservable(memoizedObservable, void 0);
|
|
91
|
+
};
|
|
85
92
|
function useAssets() {
|
|
86
|
-
const documentStore = sanity.useDocumentStore(), [sort, setSort] = React.useState("createdDesc"), [searchQuery, setSearchQuery] = React.useState(""),
|
|
93
|
+
const documentStore = sanity.useDocumentStore(), [sort, setSort] = React.useState("createdDesc"), [searchQuery, setSearchQuery] = React.useState(""), assetDocumentsObservable = useAssetDocuments({ documentStore, sort, searchQuery }), isLoading = assetDocumentsObservable === void 0;
|
|
87
94
|
return {
|
|
88
95
|
assets: React.useMemo(
|
|
89
96
|
() => (
|
|
90
97
|
// Avoid displaying both drafts & published assets by collating them together and giving preference to drafts
|
|
91
|
-
sanity.collate(
|
|
98
|
+
sanity.collate(assetDocumentsObservable ?? []).map(
|
|
92
99
|
(collated) => ({
|
|
93
100
|
...collated.draft || collated.published || {},
|
|
94
101
|
_id: collated.id
|
|
95
102
|
})
|
|
96
103
|
)
|
|
97
104
|
),
|
|
98
|
-
[
|
|
105
|
+
[assetDocumentsObservable]
|
|
99
106
|
),
|
|
100
107
|
isLoading,
|
|
101
108
|
sort,
|
|
@@ -343,6 +350,17 @@ function getPlaybackId(asset) {
|
|
|
343
350
|
function getPlaybackPolicy(asset) {
|
|
344
351
|
return asset.data?.playback_ids?.find((playbackId) => asset.playbackId === playbackId.id)?.policy ?? "public";
|
|
345
352
|
}
|
|
353
|
+
function createUrlParamsObject(client, asset, params, audience) {
|
|
354
|
+
const playbackId = getPlaybackId(asset);
|
|
355
|
+
let searchParams = new URLSearchParams(
|
|
356
|
+
JSON.parse(JSON.stringify(params, (_, v) => v ?? void 0))
|
|
357
|
+
);
|
|
358
|
+
if (getPlaybackPolicy(asset) === "signed") {
|
|
359
|
+
const token = generateJwt(client, playbackId, audience, params);
|
|
360
|
+
searchParams = new URLSearchParams({ token });
|
|
361
|
+
}
|
|
362
|
+
return { playbackId, searchParams };
|
|
363
|
+
}
|
|
346
364
|
function getAnimatedPosterSrc({
|
|
347
365
|
asset,
|
|
348
366
|
client,
|
|
@@ -352,16 +370,22 @@ function getAnimatedPosterSrc({
|
|
|
352
370
|
end = start + 5,
|
|
353
371
|
fps = 15
|
|
354
372
|
}) {
|
|
355
|
-
const params = { height, width, start, end, fps }, playbackId =
|
|
356
|
-
let searchParams = new URLSearchParams(
|
|
357
|
-
JSON.parse(JSON.stringify(params, (_, v) => v ?? void 0))
|
|
358
|
-
);
|
|
359
|
-
if (getPlaybackPolicy(asset) === "signed") {
|
|
360
|
-
const token = generateJwt(client, playbackId, "g", params);
|
|
361
|
-
searchParams = new URLSearchParams({ token });
|
|
362
|
-
}
|
|
373
|
+
const params = { height, width, start, end, fps }, { playbackId, searchParams } = createUrlParamsObject(client, asset, params, "g");
|
|
363
374
|
return `https://image.mux.com/${playbackId}/animated.gif?${searchParams}`;
|
|
364
375
|
}
|
|
376
|
+
function getPosterSrc({
|
|
377
|
+
asset,
|
|
378
|
+
client,
|
|
379
|
+
fit_mode,
|
|
380
|
+
height,
|
|
381
|
+
time = asset.thumbTime ?? void 0,
|
|
382
|
+
width
|
|
383
|
+
}) {
|
|
384
|
+
const params = { fit_mode, height, width };
|
|
385
|
+
time && (params.time = time);
|
|
386
|
+
const { playbackId, searchParams } = createUrlParamsObject(client, asset, params, "t");
|
|
387
|
+
return `https://image.mux.com/${playbackId}/thumbnail.png?${searchParams}`;
|
|
388
|
+
}
|
|
365
389
|
const Image = styledComponents.styled.img`
|
|
366
390
|
transition: opacity 0.175s ease-out 0s;
|
|
367
391
|
display: block;
|
|
@@ -376,16 +400,18 @@ const Image = styledComponents.styled.img`
|
|
|
376
400
|
};
|
|
377
401
|
function VideoThumbnail({
|
|
378
402
|
asset,
|
|
379
|
-
width
|
|
403
|
+
width,
|
|
404
|
+
staticImage = !1
|
|
380
405
|
}) {
|
|
381
|
-
const { inView, ref } = useInView(), posterWidth = width || 250, [status, setStatus] = React.useState("loading"), client = useClient(),
|
|
406
|
+
const { inView, ref } = useInView(), posterWidth = width || 250, [status, setStatus] = React.useState("loading"), client = useClient(), src = React.useMemo(() => {
|
|
382
407
|
try {
|
|
383
|
-
|
|
408
|
+
let thumbnail;
|
|
409
|
+
return staticImage ? thumbnail = getPosterSrc({ asset, client, width: posterWidth }) : thumbnail = getAnimatedPosterSrc({ asset, client, width: posterWidth }), thumbnail;
|
|
384
410
|
} catch {
|
|
385
411
|
status !== "error" && setStatus("error");
|
|
386
412
|
return;
|
|
387
413
|
}
|
|
388
|
-
}, [asset, client, posterWidth, status]);
|
|
414
|
+
}, [asset, client, posterWidth, status, staticImage]);
|
|
389
415
|
function handleLoad() {
|
|
390
416
|
setStatus("loaded");
|
|
391
417
|
}
|
|
@@ -440,8 +466,8 @@ function VideoThumbnail({
|
|
|
440
466
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
441
467
|
Image,
|
|
442
468
|
{
|
|
443
|
-
src
|
|
444
|
-
alt: `Preview for video ${asset.filename || asset.assetId}`,
|
|
469
|
+
src,
|
|
470
|
+
alt: `Preview for ${staticImage ? "image" : "video"} ${asset.filename || asset.assetId}`,
|
|
445
471
|
onLoad: handleLoad,
|
|
446
472
|
onError: handleError,
|
|
447
473
|
style: {
|
|
@@ -754,6 +780,14 @@ function StopWatchIcon(props) {
|
|
|
754
780
|
}
|
|
755
781
|
);
|
|
756
782
|
}
|
|
783
|
+
const DialogStateContext = React.createContext({
|
|
784
|
+
dialogState: !1,
|
|
785
|
+
setDialogState: () => null
|
|
786
|
+
}), DialogStateProvider = ({
|
|
787
|
+
dialogState,
|
|
788
|
+
setDialogState,
|
|
789
|
+
children
|
|
790
|
+
}) => /* @__PURE__ */ jsxRuntime.jsx(DialogStateContext.Provider, { value: { dialogState, setDialogState }, children }), useDialogStateContext = () => React.useContext(DialogStateContext);
|
|
757
791
|
function getVideoSrc({ asset, client }) {
|
|
758
792
|
const playbackId = getPlaybackId(asset), searchParams = new URLSearchParams();
|
|
759
793
|
if (getPlaybackPolicy(asset) === "signed") {
|
|
@@ -762,12 +796,100 @@ function getVideoSrc({ asset, client }) {
|
|
|
762
796
|
}
|
|
763
797
|
return `https://stream.mux.com/${playbackId}.m3u8?${searchParams}`;
|
|
764
798
|
}
|
|
799
|
+
function getDevicePixelRatio(options) {
|
|
800
|
+
const {
|
|
801
|
+
defaultDpr = 1,
|
|
802
|
+
maxDpr = 3,
|
|
803
|
+
round = !0
|
|
804
|
+
} = options || {}, dpr = typeof window < "u" && typeof window.devicePixelRatio == "number" ? window.devicePixelRatio : defaultDpr;
|
|
805
|
+
return Math.min(Math.max(1, round ? Math.floor(dpr) : dpr), maxDpr);
|
|
806
|
+
}
|
|
807
|
+
function formatSeconds(seconds) {
|
|
808
|
+
if (typeof seconds != "number" || Number.isNaN(seconds))
|
|
809
|
+
return "";
|
|
810
|
+
const hrs = ~~(seconds / 3600), mins = ~~(seconds % 3600 / 60), secs = ~~seconds % 60;
|
|
811
|
+
let ret = "";
|
|
812
|
+
return hrs > 0 && (ret += "" + hrs + ":" + (mins < 10 ? "0" : "")), ret += "" + mins + ":" + (secs < 10 ? "0" : ""), ret += "" + secs, ret;
|
|
813
|
+
}
|
|
814
|
+
function formatSecondsToHHMMSS(seconds) {
|
|
815
|
+
const hrs = Math.floor(seconds / 3600).toString().padStart(2, "0"), mins = Math.floor(seconds % 3600 / 60).toString().padStart(2, "0"), secs = Math.floor(seconds % 60).toString().padStart(2, "0");
|
|
816
|
+
return `${hrs}:${mins}:${secs}`;
|
|
817
|
+
}
|
|
818
|
+
function isValidTimeFormat(time) {
|
|
819
|
+
return /^([0-1]?[0-9]|2[0-3]):([0-5]?[0-9]):([0-5]?[0-9])$/.test(time) || time === "";
|
|
820
|
+
}
|
|
821
|
+
function getSecondsFromTimeFormat(time) {
|
|
822
|
+
const [hh = 0, mm = 0, ss = 0] = time.split(":").map(Number);
|
|
823
|
+
return hh * 3600 + mm * 60 + ss;
|
|
824
|
+
}
|
|
825
|
+
function EditThumbnailDialog({ asset, currentTime = 0 }) {
|
|
826
|
+
const client = useClient(), { setDialogState } = useDialogStateContext(), dialogId = `EditThumbnailDialog${React.useId()}`, [timeFormatted, setTimeFormatted] = React.useState(
|
|
827
|
+
() => formatSecondsToHHMMSS(currentTime)
|
|
828
|
+
), [nextTime, setNextTime] = React.useState(currentTime), [inputError, setInputError] = React.useState(""), assetWithNewThumbnail = React.useMemo(() => ({ ...asset, thumbTime: nextTime }), [asset, nextTime]), [saving, setSaving] = React.useState(!1), [saveThumbnailError, setSaveThumbnailError] = React.useState(null), handleSave = () => {
|
|
829
|
+
setSaving(!0), client.patch(asset._id).set({ thumbTime: nextTime }).commit({ returnDocuments: !1 }).then(() => void setDialogState(!1)).catch(setSaveThumbnailError).finally(() => void setSaving(!1));
|
|
830
|
+
}, width = 300 * getDevicePixelRatio({ maxDpr: 2 });
|
|
831
|
+
if (saveThumbnailError)
|
|
832
|
+
throw saveThumbnailError;
|
|
833
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
834
|
+
ui.Dialog,
|
|
835
|
+
{
|
|
836
|
+
id: dialogId,
|
|
837
|
+
header: "Edit thumbnail",
|
|
838
|
+
onClose: () => setDialogState(!1),
|
|
839
|
+
footer: /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, { padding: 3, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
840
|
+
ui.Button,
|
|
841
|
+
{
|
|
842
|
+
disabled: inputError !== "",
|
|
843
|
+
mode: "ghost",
|
|
844
|
+
tone: "primary",
|
|
845
|
+
loading: saving,
|
|
846
|
+
onClick: handleSave,
|
|
847
|
+
text: "Set new thumbnail"
|
|
848
|
+
},
|
|
849
|
+
"thumbnail"
|
|
850
|
+
) }),
|
|
851
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, padding: 3, children: [
|
|
852
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
|
|
853
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, weight: "semibold", children: "Current:" }),
|
|
854
|
+
/* @__PURE__ */ jsxRuntime.jsx(VideoThumbnail, { asset, width, staticImage: !0 })
|
|
855
|
+
] }),
|
|
856
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
|
|
857
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, weight: "semibold", children: "New:" }),
|
|
858
|
+
/* @__PURE__ */ jsxRuntime.jsx(VideoThumbnail, { asset: assetWithNewThumbnail, width, staticImage: !0 })
|
|
859
|
+
] }),
|
|
860
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Stack, { space: 2, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { align: "center", justify: "center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 5, weight: "semibold", children: "Or" }) }) }),
|
|
861
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
|
|
862
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, weight: "semibold", children: "Selected time for thumbnail (hh:mm:ss):" }),
|
|
863
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
864
|
+
ui.TextInput,
|
|
865
|
+
{
|
|
866
|
+
size: 1,
|
|
867
|
+
value: timeFormatted,
|
|
868
|
+
placeholder: "hh:mm:ss",
|
|
869
|
+
onChange: (event) => {
|
|
870
|
+
const value = event.currentTarget.value;
|
|
871
|
+
if (setTimeFormatted(value), isValidTimeFormat(value)) {
|
|
872
|
+
setInputError("");
|
|
873
|
+
const totalSeconds = getSecondsFromTimeFormat(value);
|
|
874
|
+
setNextTime(totalSeconds);
|
|
875
|
+
} else
|
|
876
|
+
setInputError("Invalid time format");
|
|
877
|
+
},
|
|
878
|
+
customValidity: inputError
|
|
879
|
+
}
|
|
880
|
+
)
|
|
881
|
+
] })
|
|
882
|
+
] })
|
|
883
|
+
}
|
|
884
|
+
);
|
|
885
|
+
}
|
|
765
886
|
function VideoPlayer({
|
|
766
887
|
asset,
|
|
888
|
+
thumbnailWidth = 250,
|
|
767
889
|
children,
|
|
768
890
|
...props
|
|
769
891
|
}) {
|
|
770
|
-
const client = useClient(), isAudio = assetIsAudio(asset), { src: videoSrc, error } = React.useMemo(() => {
|
|
892
|
+
const client = useClient(), { dialogState } = useDialogStateContext(), isAudio = assetIsAudio(asset), muxPlayer = React.useRef(null), thumbnail = getPosterSrc({ asset, client, width: thumbnailWidth }), { src: videoSrc, error } = React.useMemo(() => {
|
|
771
893
|
try {
|
|
772
894
|
const src = asset?.playbackId && getVideoSrc({ client, asset });
|
|
773
895
|
return src ? { src } : { error: new TypeError("Asset has no playback ID") };
|
|
@@ -785,49 +907,54 @@ function VideoPlayer({
|
|
|
785
907
|
return isAudio && (aspectRatio = props.forceAspectRatio ? (
|
|
786
908
|
// Make it wider when forcing aspect ratio to balance with videos' rendering height (audio players overflow a bit)
|
|
787
909
|
props.forceAspectRatio * 1.2
|
|
788
|
-
) : AUDIO_ASPECT_RATIO), /* @__PURE__ */ jsxRuntime.jsxs(
|
|
789
|
-
|
|
790
|
-
/* @__PURE__ */ jsxRuntime.
|
|
791
|
-
|
|
910
|
+
) : AUDIO_ASPECT_RATIO), /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
911
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Card, { tone: "transparent", style: { aspectRatio, position: "relative" }, children: [
|
|
912
|
+
videoSrc && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
913
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
914
|
+
MuxPlayer__default.default,
|
|
915
|
+
{
|
|
916
|
+
poster: thumbnail,
|
|
917
|
+
ref: muxPlayer,
|
|
918
|
+
...props,
|
|
919
|
+
playsInline: !0,
|
|
920
|
+
playbackId: asset.playbackId,
|
|
921
|
+
tokens: signedToken ? { playback: signedToken, thumbnail: signedToken, storyboard: signedToken } : void 0,
|
|
922
|
+
preload: "metadata",
|
|
923
|
+
crossOrigin: "anonymous",
|
|
924
|
+
metadata: {
|
|
925
|
+
player_name: "Sanity Admin Dashboard",
|
|
926
|
+
player_version: "2.5.0",
|
|
927
|
+
page_type: "Preview Player"
|
|
928
|
+
},
|
|
929
|
+
audio: isAudio,
|
|
930
|
+
style: {
|
|
931
|
+
height: "100%",
|
|
932
|
+
width: "100%",
|
|
933
|
+
display: "block",
|
|
934
|
+
objectFit: "contain"
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
),
|
|
938
|
+
children
|
|
939
|
+
] }),
|
|
940
|
+
error ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
941
|
+
"div",
|
|
792
942
|
{
|
|
793
|
-
...props,
|
|
794
|
-
playsInline: !0,
|
|
795
|
-
playbackId: asset.playbackId,
|
|
796
|
-
tokens: signedToken ? { playback: signedToken, thumbnail: signedToken, storyboard: signedToken } : void 0,
|
|
797
|
-
preload: "metadata",
|
|
798
|
-
crossOrigin: "anonymous",
|
|
799
|
-
metadata: {
|
|
800
|
-
player_name: "Sanity Admin Dashboard",
|
|
801
|
-
player_version: "2.4.0",
|
|
802
|
-
page_type: "Preview Player"
|
|
803
|
-
},
|
|
804
|
-
audio: isAudio,
|
|
805
943
|
style: {
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
}
|
|
944
|
+
position: "absolute",
|
|
945
|
+
top: "50%",
|
|
946
|
+
left: "50%",
|
|
947
|
+
transform: "translate(-50%, -50%)"
|
|
948
|
+
},
|
|
949
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { muted: !0, children: [
|
|
950
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.ErrorOutlineIcon, { style: { marginRight: "0.15em" } }),
|
|
951
|
+
typeof error == "object" && "message" in error && typeof error.message == "string" ? error.message : "Error loading video"
|
|
952
|
+
] })
|
|
811
953
|
}
|
|
812
|
-
),
|
|
954
|
+
) : null,
|
|
813
955
|
children
|
|
814
956
|
] }),
|
|
815
|
-
|
|
816
|
-
"div",
|
|
817
|
-
{
|
|
818
|
-
style: {
|
|
819
|
-
position: "absolute",
|
|
820
|
-
top: "50%",
|
|
821
|
-
left: "50%",
|
|
822
|
-
transform: "translate(-50%, -50%)"
|
|
823
|
-
},
|
|
824
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { muted: !0, children: [
|
|
825
|
-
/* @__PURE__ */ jsxRuntime.jsx(icons.ErrorOutlineIcon, { style: { marginRight: "0.15em" } }),
|
|
826
|
-
typeof error == "object" && "message" in error && typeof error.message == "string" ? error.message : "Error loading video"
|
|
827
|
-
] })
|
|
828
|
-
}
|
|
829
|
-
) : null,
|
|
830
|
-
children
|
|
957
|
+
dialogState === "edit-thumbnail" && /* @__PURE__ */ jsxRuntime.jsx(EditThumbnailDialog, { asset, currentTime: muxPlayer?.current?.currentTime })
|
|
831
958
|
] });
|
|
832
959
|
}
|
|
833
960
|
function assetIsAudio(asset) {
|
|
@@ -1150,13 +1277,6 @@ const useDocReferences = sanity.createHookFromObservableFactory(({ documentStore
|
|
|
1150
1277
|
apiVersion: SANITY_API_VERSION
|
|
1151
1278
|
}
|
|
1152
1279
|
));
|
|
1153
|
-
function formatSeconds(seconds) {
|
|
1154
|
-
if (typeof seconds != "number" || Number.isNaN(seconds))
|
|
1155
|
-
return "";
|
|
1156
|
-
const hrs = ~~(seconds / 3600), mins = ~~(seconds % 3600 / 60), secs = ~~seconds % 60;
|
|
1157
|
-
let ret = "";
|
|
1158
|
-
return hrs > 0 && (ret += "" + hrs + ":" + (mins < 10 ? "0" : "")), ret += "" + mins + ":" + (secs < 10 ? "0" : ""), ret += "" + secs, ret;
|
|
1159
|
-
}
|
|
1160
1280
|
function getVideoMetadata(doc) {
|
|
1161
1281
|
const id = doc.assetId || doc._id || "", date = doc.data?.created_at ? new Date(Number(doc.data.created_at) * 1e3) : new Date(doc._createdAt || doc._updatedAt || Date.now());
|
|
1162
1282
|
return {
|
|
@@ -3279,7 +3399,7 @@ const FileButton = styledComponents.styled(ui.MenuItem)(({ theme }) => {
|
|
|
3279
3399
|
`, LockButton = styledComponents.styled(ui.Button)`
|
|
3280
3400
|
background: transparent;
|
|
3281
3401
|
color: white;
|
|
3282
|
-
|
|
3402
|
+
`, isVideoAsset = (asset) => asset._type === "mux.videoAsset";
|
|
3283
3403
|
function PlayerActionsMenu(props) {
|
|
3284
3404
|
const { asset, readOnly, dialogState, setDialogState, onChange, onSelect } = props, [open, setOpen] = React.useState(!1), [menuElement, setMenuRef] = React.useState(null), isSigned = React.useMemo(() => getPlaybackPolicy(asset) === "signed", [asset]), onReset = React.useCallback(() => onChange(sanity.PatchEvent.from(sanity.unset([]))), [onChange]);
|
|
3285
3405
|
return React.useEffect(() => {
|
|
@@ -3323,6 +3443,14 @@ function PlayerActionsMenu(props) {
|
|
|
3323
3443
|
onClick: () => setDialogState("select-video")
|
|
3324
3444
|
}
|
|
3325
3445
|
),
|
|
3446
|
+
isVideoAsset(asset) && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3447
|
+
ui.MenuItem,
|
|
3448
|
+
{
|
|
3449
|
+
icon: icons.ImageIcon,
|
|
3450
|
+
text: "Thumbnail",
|
|
3451
|
+
onClick: () => setDialogState("edit-thumbnail")
|
|
3452
|
+
}
|
|
3453
|
+
),
|
|
3326
3454
|
/* @__PURE__ */ jsxRuntime.jsx(ui.MenuDivider, {}),
|
|
3327
3455
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3328
3456
|
ui.MenuItem,
|
|
@@ -3941,7 +4069,7 @@ function Uploader(props) {
|
|
|
3941
4069
|
case "commitUpload":
|
|
3942
4070
|
return Object.assign({}, prev, { uploadStatus: { progress: 0 } });
|
|
3943
4071
|
case "progressInfo": {
|
|
3944
|
-
const {
|
|
4072
|
+
const { ...payload } = action;
|
|
3945
4073
|
return Object.assign({}, prev, {
|
|
3946
4074
|
uploadStatus: {
|
|
3947
4075
|
...prev.uploadStatus,
|
|
@@ -4060,7 +4188,7 @@ function Uploader(props) {
|
|
|
4060
4188
|
idx > -1 && dragEnteredEls.current.splice(idx, 1), dragEnteredEls.current.length === 0 && setDragState(null);
|
|
4061
4189
|
};
|
|
4062
4190
|
if (state.error !== null) {
|
|
4063
|
-
const error = {
|
|
4191
|
+
const error = {};
|
|
4064
4192
|
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { gap: 3, direction: "column", justify: "center", align: "center", children: [
|
|
4065
4193
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 5, muted: !0, children: /* @__PURE__ */ jsxRuntime.jsx(icons.ErrorOutlineIcon, {}) }),
|
|
4066
4194
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: "Something went wrong" }),
|
|
@@ -4103,20 +4231,27 @@ function Uploader(props) {
|
|
|
4103
4231
|
onPaste: handlePaste,
|
|
4104
4232
|
ref: containerRef,
|
|
4105
4233
|
children: props.asset ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
4106
|
-
|
|
4234
|
+
DialogStateProvider,
|
|
4107
4235
|
{
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
PlayerActionsMenu$1,
|
|
4236
|
+
dialogState: props.dialogState,
|
|
4237
|
+
setDialogState: props.setDialogState,
|
|
4238
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4239
|
+
Player,
|
|
4113
4240
|
{
|
|
4241
|
+
readOnly: props.readOnly,
|
|
4114
4242
|
asset: props.asset,
|
|
4115
|
-
dialogState: props.dialogState,
|
|
4116
|
-
setDialogState: props.setDialogState,
|
|
4117
4243
|
onChange: props.onChange,
|
|
4118
|
-
|
|
4119
|
-
|
|
4244
|
+
buttons: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4245
|
+
PlayerActionsMenu$1,
|
|
4246
|
+
{
|
|
4247
|
+
asset: props.asset,
|
|
4248
|
+
dialogState: props.dialogState,
|
|
4249
|
+
setDialogState: props.setDialogState,
|
|
4250
|
+
onChange: props.onChange,
|
|
4251
|
+
onSelect: handleUpload,
|
|
4252
|
+
readOnly: props.readOnly
|
|
4253
|
+
}
|
|
4254
|
+
)
|
|
4120
4255
|
}
|
|
4121
4256
|
)
|
|
4122
4257
|
}
|