sanity-plugin-mux-input 2.15.0 → 2.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1150 -177
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1151 -178
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/actions/upload.ts +42 -4
- package/src/components/DraggableWatermark.tsx +877 -0
- package/src/components/PlayerActionsMenu.tsx +14 -0
- package/src/components/ResyncMetadata.tsx +152 -73
- package/src/components/TextTracksManager.tsx +11 -55
- package/src/components/UploadConfiguration.tsx +259 -59
- package/src/components/Uploader.tsx +7 -1
- package/src/components/VideoDetails/VideoDetails.tsx +27 -11
- package/src/components/VideoDetails/useVideoDetails.ts +15 -1
- package/src/components/VideoPlayer.tsx +2 -0
- package/src/hooks/useMediaMetadata.ts +3 -0
- package/src/hooks/useResyncAsset.ts +110 -0
- package/src/hooks/useResyncMuxMetadata.ts +33 -0
- package/src/schema.ts +5 -0
- package/src/util/addKeysToMuxData.ts +30 -0
- package/src/util/convertWatermarkToMux.ts +160 -0
- package/src/util/roundPxString.ts +16 -0
- package/src/util/types.ts +43 -1
|
@@ -2,11 +2,12 @@ import {DocumentVideoIcon, ErrorOutlineIcon, UploadIcon} from '@sanity/icons'
|
|
|
2
2
|
import {Box, Button, Card, Dialog, Flex, Label, Radio, Stack, Text} from '@sanity/ui'
|
|
3
3
|
import {uuid} from '@sanity/uuid'
|
|
4
4
|
import LanguagesList from 'iso-639-1'
|
|
5
|
-
import {useEffect, useId, useReducer, useRef, useState} from 'react'
|
|
5
|
+
import {memo, useEffect, useId, useReducer, useRef, useState} from 'react'
|
|
6
6
|
import {FormField} from 'sanity'
|
|
7
7
|
|
|
8
8
|
import {useFetchFileSize} from '../hooks/useFetchFileSize'
|
|
9
|
-
import {useMediaMetadata} from '../hooks/useMediaMetadata'
|
|
9
|
+
import {useMediaMetadata, type VideoAssetMetadata} from '../hooks/useMediaMetadata'
|
|
10
|
+
import {convertWatermarkToMuxOverlay} from '../util/convertWatermarkToMux'
|
|
10
11
|
import formatBytes from '../util/formatBytes'
|
|
11
12
|
import {formatSeconds} from '../util/formatSeconds'
|
|
12
13
|
import {
|
|
@@ -21,7 +22,9 @@ import {
|
|
|
21
22
|
type SupportedMuxLanguage,
|
|
22
23
|
type UploadConfig,
|
|
23
24
|
type UploadTextTrack,
|
|
25
|
+
WatermarkConfig,
|
|
24
26
|
} from '../util/types'
|
|
27
|
+
import DraggableWatermark, {WatermarkControls} from './DraggableWatermark'
|
|
25
28
|
import TextTracksEditor, {type TrackAction} from './TextTracksEditor'
|
|
26
29
|
import PlaybackPolicy from './uploadConfiguration/PlaybackPolicy'
|
|
27
30
|
import {
|
|
@@ -39,6 +42,7 @@ export type UploadConfigurationStateAction =
|
|
|
39
42
|
| {action: 'signed_policy'; value: UploadConfig['signed_policy']}
|
|
40
43
|
| {action: 'public_policy'; value: UploadConfig['public_policy']}
|
|
41
44
|
| {action: 'drm_policy'; value: UploadConfig['drm_policy']}
|
|
45
|
+
| {action: 'watermark'; value: WatermarkConfig}
|
|
42
46
|
| TrackAction
|
|
43
47
|
|
|
44
48
|
const VIDEO_QUALITY_LEVELS = [
|
|
@@ -80,10 +84,13 @@ export default function UploadConfiguration({
|
|
|
80
84
|
stagedUpload: StagedUpload
|
|
81
85
|
secrets: Secrets
|
|
82
86
|
pluginConfig: PluginConfig
|
|
83
|
-
startUpload: (settings: MuxNewAssetSettings) => void
|
|
87
|
+
startUpload: (settings: MuxNewAssetSettings, watermark: WatermarkConfig | undefined) => void
|
|
84
88
|
onClose: () => void
|
|
85
89
|
}) {
|
|
86
90
|
const id = useId()
|
|
91
|
+
const [watermarkValidationError, setWatermarkValidationError] = useState<string | null>(null)
|
|
92
|
+
const watermarkPreviewContainerRef = useRef<HTMLDivElement>(null)
|
|
93
|
+
const watermarkPreviewVideoRef = useRef<HTMLVideoElement>(null)
|
|
87
94
|
const autoTextTracks = useRef<NonNullable<UploadConfig['text_tracks']>>(
|
|
88
95
|
pluginConfig.video_quality === 'plus' && pluginConfig.defaultAutogeneratedSubtitleLang
|
|
89
96
|
? [
|
|
@@ -130,6 +137,8 @@ export default function UploadConfiguration({
|
|
|
130
137
|
return Object.assign({}, prev, {[action.action]: action.value})
|
|
131
138
|
case 'drm_policy':
|
|
132
139
|
return Object.assign({}, prev, {[action.action]: action.value})
|
|
140
|
+
case 'watermark':
|
|
141
|
+
return Object.assign({}, prev, {watermark: action.value})
|
|
133
142
|
// Updating individual tracks
|
|
134
143
|
case 'track': {
|
|
135
144
|
const text_tracks = [...prev.text_tracks]
|
|
@@ -254,7 +263,12 @@ export default function UploadConfiguration({
|
|
|
254
263
|
const {disableTextTrackConfig, disableUploadConfig} = pluginConfig
|
|
255
264
|
const skipConfig = disableTextTrackConfig && disableUploadConfig
|
|
256
265
|
useEffect(() => {
|
|
257
|
-
if (skipConfig)
|
|
266
|
+
if (skipConfig) {
|
|
267
|
+
const {settings, watermark} = formatUploadConfig(config, secrets, {
|
|
268
|
+
videoAspectRatio: videoAssetMetadata?.aspectRatio,
|
|
269
|
+
})
|
|
270
|
+
startUpload(settings, watermark)
|
|
271
|
+
}
|
|
258
272
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
259
273
|
}, [])
|
|
260
274
|
if (skipConfig) return null
|
|
@@ -275,7 +289,7 @@ export default function UploadConfiguration({
|
|
|
275
289
|
onClose={onClose}
|
|
276
290
|
>
|
|
277
291
|
<Stack padding={4} space={2}>
|
|
278
|
-
{validationError && (
|
|
292
|
+
{(validationError || watermarkValidationError) && (
|
|
279
293
|
<Card padding={3} tone="critical" radius={2} marginBottom={2}>
|
|
280
294
|
<Flex gap={2} align="flex-start">
|
|
281
295
|
<ErrorOutlineIcon width={20} height={20} />
|
|
@@ -283,7 +297,7 @@ export default function UploadConfiguration({
|
|
|
283
297
|
<Text size={1} weight="semibold">
|
|
284
298
|
Validation Error
|
|
285
299
|
</Text>
|
|
286
|
-
<Text size={1}>{validationError}</Text>
|
|
300
|
+
<Text size={1}>{validationError || watermarkValidationError}</Text>
|
|
287
301
|
</Stack>
|
|
288
302
|
</Flex>
|
|
289
303
|
</Card>
|
|
@@ -377,27 +391,38 @@ export default function UploadConfiguration({
|
|
|
377
391
|
</FormField>
|
|
378
392
|
|
|
379
393
|
{!basicConfig && (
|
|
380
|
-
|
|
381
|
-
<
|
|
382
|
-
<
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
394
|
+
<>
|
|
395
|
+
<FormField title="Additional Configuration">
|
|
396
|
+
<Stack space={3}>
|
|
397
|
+
<PlaybackPolicy id={id} config={config} secrets={secrets} dispatch={dispatch} />
|
|
398
|
+
{maxSupportedResolution > 0 && (
|
|
399
|
+
<ResolutionTierSelector
|
|
400
|
+
id={id}
|
|
401
|
+
config={config}
|
|
402
|
+
dispatch={dispatch}
|
|
403
|
+
maxSupportedResolution={maxSupportedResolution}
|
|
404
|
+
/>
|
|
405
|
+
)}
|
|
406
|
+
<StaticRenditionSelector id={id} config={config} dispatch={dispatch} />
|
|
407
|
+
{!disableTextTrackConfig && (
|
|
408
|
+
<TextTracksEditor
|
|
409
|
+
tracks={config.text_tracks}
|
|
410
|
+
dispatch={dispatch}
|
|
411
|
+
defaultLang={pluginConfig.defaultAutogeneratedSubtitleLang}
|
|
412
|
+
/>
|
|
413
|
+
)}
|
|
414
|
+
</Stack>
|
|
415
|
+
</FormField>
|
|
416
|
+
<WatermarkSection
|
|
417
|
+
config={config}
|
|
418
|
+
dispatch={dispatch}
|
|
419
|
+
stagedUpload={stagedUpload}
|
|
420
|
+
videoAssetMetadata={videoAssetMetadata}
|
|
421
|
+
watermarkPreviewContainerRef={watermarkPreviewContainerRef}
|
|
422
|
+
watermarkPreviewVideoRef={watermarkPreviewVideoRef}
|
|
423
|
+
onValidationChange={setWatermarkValidationError}
|
|
424
|
+
/>
|
|
425
|
+
</>
|
|
401
426
|
)}
|
|
402
427
|
</Stack>
|
|
403
428
|
)}
|
|
@@ -415,7 +440,10 @@ export default function UploadConfiguration({
|
|
|
415
440
|
tone="positive"
|
|
416
441
|
onClick={() => {
|
|
417
442
|
if (!validationError) {
|
|
418
|
-
|
|
443
|
+
const {settings, watermark} = formatUploadConfig(config, secrets, {
|
|
444
|
+
videoAspectRatio: videoAssetMetadata?.aspectRatio,
|
|
445
|
+
})
|
|
446
|
+
startUpload(settings, watermark)
|
|
419
447
|
}
|
|
420
448
|
}}
|
|
421
449
|
/>
|
|
@@ -449,7 +477,14 @@ function setAdvancedPlaybackPolicy(
|
|
|
449
477
|
return advanced_playback_policies
|
|
450
478
|
}
|
|
451
479
|
|
|
452
|
-
function formatUploadConfig(
|
|
480
|
+
function formatUploadConfig(
|
|
481
|
+
config: UploadConfig,
|
|
482
|
+
secrets: Secrets,
|
|
483
|
+
options?: {videoAspectRatio?: number | null}
|
|
484
|
+
): {
|
|
485
|
+
settings: MuxNewAssetSettings
|
|
486
|
+
watermark?: WatermarkConfig
|
|
487
|
+
} {
|
|
453
488
|
const generated_subtitles = config.text_tracks
|
|
454
489
|
.filter<AutogeneratedTextTrack>(isAutogeneratedTrack)
|
|
455
490
|
.map<{name: string; language_code: SupportedMuxLanguage}>((track) => ({
|
|
@@ -457,36 +492,201 @@ function formatUploadConfig(config: UploadConfig, secrets: Secrets): MuxNewAsset
|
|
|
457
492
|
language_code: track.language_code,
|
|
458
493
|
}))
|
|
459
494
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
495
|
+
const inputs: NonNullable<MuxNewAssetSettings['input']> = [
|
|
496
|
+
{
|
|
497
|
+
type: 'video',
|
|
498
|
+
generated_subtitles: generated_subtitles.length > 0 ? generated_subtitles : undefined,
|
|
499
|
+
},
|
|
500
|
+
...config.text_tracks.filter<CustomTextTrack>(isCustomTextTrack).reduce(
|
|
501
|
+
(acc, track) => {
|
|
502
|
+
if (track.language_code && track.file && track.name) {
|
|
503
|
+
acc.push({
|
|
504
|
+
url: track.file.contents,
|
|
505
|
+
type: 'text',
|
|
506
|
+
text_type: track.type === 'subtitles' ? 'subtitles' : undefined,
|
|
507
|
+
language_code: track.language_code,
|
|
508
|
+
name: track.name,
|
|
509
|
+
closed_captions: track.type === 'captions',
|
|
510
|
+
})
|
|
511
|
+
}
|
|
512
|
+
return acc
|
|
465
513
|
},
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
],
|
|
483
|
-
static_renditions:
|
|
484
|
-
config.static_renditions.length > 0
|
|
485
|
-
? config.static_renditions.map((resolution) => ({resolution}))
|
|
486
|
-
: undefined,
|
|
487
|
-
advanced_playback_policies: setAdvancedPlaybackPolicy(config, secrets),
|
|
488
|
-
max_resolution_tier: config.max_resolution_tier,
|
|
489
|
-
video_quality: config.video_quality,
|
|
490
|
-
normalize_audio: config.normalize_audio,
|
|
514
|
+
[] as NonNullable<MuxNewAssetSettings['input']>
|
|
515
|
+
),
|
|
516
|
+
]
|
|
517
|
+
|
|
518
|
+
if (config.watermark?.imageUrl) {
|
|
519
|
+
const watermarkForMux: WatermarkConfig = {...config.watermark, enabled: true}
|
|
520
|
+
const overlaySettings = convertWatermarkToMuxOverlay(watermarkForMux, {
|
|
521
|
+
videoAspectRatio: options?.videoAspectRatio ?? undefined,
|
|
522
|
+
units: 'px',
|
|
523
|
+
})
|
|
524
|
+
if (overlaySettings) {
|
|
525
|
+
inputs.push({
|
|
526
|
+
url: config.watermark.imageUrl,
|
|
527
|
+
overlay_settings: overlaySettings,
|
|
528
|
+
} as NonNullable<MuxNewAssetSettings['input']>[number])
|
|
529
|
+
}
|
|
491
530
|
}
|
|
531
|
+
|
|
532
|
+
return {
|
|
533
|
+
settings: {
|
|
534
|
+
input: inputs,
|
|
535
|
+
static_renditions:
|
|
536
|
+
config.static_renditions.length > 0
|
|
537
|
+
? config.static_renditions.map((resolution) => ({resolution}))
|
|
538
|
+
: undefined,
|
|
539
|
+
advanced_playback_policies: setAdvancedPlaybackPolicy(config, secrets),
|
|
540
|
+
max_resolution_tier: config.max_resolution_tier,
|
|
541
|
+
video_quality: config.video_quality,
|
|
542
|
+
normalize_audio: config.normalize_audio,
|
|
543
|
+
},
|
|
544
|
+
watermark: config.watermark?.imageUrl
|
|
545
|
+
? ({...config.watermark, enabled: true} as WatermarkConfig)
|
|
546
|
+
: undefined,
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function WatermarkSection({
|
|
551
|
+
config,
|
|
552
|
+
dispatch,
|
|
553
|
+
stagedUpload,
|
|
554
|
+
videoAssetMetadata,
|
|
555
|
+
watermarkPreviewContainerRef,
|
|
556
|
+
watermarkPreviewVideoRef,
|
|
557
|
+
onValidationChange,
|
|
558
|
+
}: {
|
|
559
|
+
config: UploadConfig
|
|
560
|
+
dispatch: (action: UploadConfigurationStateAction) => void
|
|
561
|
+
stagedUpload: StagedUpload
|
|
562
|
+
videoAssetMetadata: VideoAssetMetadata | null
|
|
563
|
+
watermarkPreviewContainerRef: React.RefObject<HTMLDivElement | null>
|
|
564
|
+
watermarkPreviewVideoRef: React.RefObject<HTMLVideoElement | null>
|
|
565
|
+
onValidationChange: (error: string | null) => void
|
|
566
|
+
}) {
|
|
567
|
+
if (videoAssetMetadata?.isAudioOnly !== false) return null
|
|
568
|
+
return (
|
|
569
|
+
<FormField
|
|
570
|
+
title="Watermark"
|
|
571
|
+
description={
|
|
572
|
+
<>
|
|
573
|
+
Add a watermark overlay to your video using Mux's native watermark support.{' '}
|
|
574
|
+
<a
|
|
575
|
+
href="https://www.mux.com/docs/guides/add-watermarks-to-your-videos"
|
|
576
|
+
target="_blank"
|
|
577
|
+
rel="noopener noreferrer"
|
|
578
|
+
>
|
|
579
|
+
Learn more about Mux watermarks.
|
|
580
|
+
</a>
|
|
581
|
+
</>
|
|
582
|
+
}
|
|
583
|
+
>
|
|
584
|
+
<Stack space={3}>
|
|
585
|
+
<WatermarkControls
|
|
586
|
+
watermark={config.watermark || {enabled: false}}
|
|
587
|
+
onChange={(watermark) => {
|
|
588
|
+
dispatch({action: 'watermark', value: watermark})
|
|
589
|
+
}}
|
|
590
|
+
onValidationChange={onValidationChange}
|
|
591
|
+
previewContainerRef={watermarkPreviewContainerRef}
|
|
592
|
+
previewVideoRef={watermarkPreviewVideoRef}
|
|
593
|
+
/>
|
|
594
|
+
{config.watermark?.imageUrl &&
|
|
595
|
+
stagedUpload.type === 'file' &&
|
|
596
|
+
// Canvas preview is only shown in "Canvas" mode (no explicit overlay_settings)
|
|
597
|
+
!config.watermark.overlay_settings && (
|
|
598
|
+
<WatermarkPreview
|
|
599
|
+
stagedUpload={stagedUpload}
|
|
600
|
+
watermark={config.watermark}
|
|
601
|
+
videoAspectRatio={videoAssetMetadata.aspectRatio}
|
|
602
|
+
onWatermarkChange={(watermark) => {
|
|
603
|
+
dispatch({action: 'watermark', value: watermark})
|
|
604
|
+
}}
|
|
605
|
+
previewContainerRef={watermarkPreviewContainerRef}
|
|
606
|
+
videoRef={watermarkPreviewVideoRef}
|
|
607
|
+
/>
|
|
608
|
+
)}
|
|
609
|
+
</Stack>
|
|
610
|
+
</FormField>
|
|
611
|
+
)
|
|
492
612
|
}
|
|
613
|
+
|
|
614
|
+
// Memoized preview component to prevent unnecessary re-renders
|
|
615
|
+
const WatermarkPreview = memo(function WatermarkPreview({
|
|
616
|
+
stagedUpload,
|
|
617
|
+
watermark,
|
|
618
|
+
onWatermarkChange,
|
|
619
|
+
videoAspectRatio,
|
|
620
|
+
previewContainerRef,
|
|
621
|
+
videoRef,
|
|
622
|
+
}: {
|
|
623
|
+
stagedUpload: StagedUpload
|
|
624
|
+
watermark: WatermarkConfig
|
|
625
|
+
onWatermarkChange: (watermark: WatermarkConfig) => void
|
|
626
|
+
videoAspectRatio?: number | null
|
|
627
|
+
previewContainerRef: React.RefObject<HTMLDivElement | null>
|
|
628
|
+
videoRef: React.RefObject<HTMLVideoElement | null>
|
|
629
|
+
}) {
|
|
630
|
+
// Initialize video source only once
|
|
631
|
+
useEffect(() => {
|
|
632
|
+
if (videoRef.current && stagedUpload.type === 'file') {
|
|
633
|
+
const file = stagedUpload.files[0]
|
|
634
|
+
const url = URL.createObjectURL(file)
|
|
635
|
+
videoRef.current.src = url
|
|
636
|
+
|
|
637
|
+
return () => {
|
|
638
|
+
URL.revokeObjectURL(url)
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return undefined
|
|
642
|
+
}, [stagedUpload, videoRef])
|
|
643
|
+
|
|
644
|
+
const isVertical =
|
|
645
|
+
videoAspectRatio !== null && videoAspectRatio !== undefined && videoAspectRatio < 1
|
|
646
|
+
|
|
647
|
+
return (
|
|
648
|
+
<Card
|
|
649
|
+
tone="transparent"
|
|
650
|
+
border
|
|
651
|
+
style={{
|
|
652
|
+
overflow: 'hidden',
|
|
653
|
+
// For vertical videos, center the preview and limit its width
|
|
654
|
+
display: 'flex',
|
|
655
|
+
justifyContent: 'center',
|
|
656
|
+
}}
|
|
657
|
+
>
|
|
658
|
+
{/* Inner container that exactly matches the video aspect ratio - no padding, no letterbox */}
|
|
659
|
+
<div
|
|
660
|
+
ref={previewContainerRef}
|
|
661
|
+
style={{
|
|
662
|
+
position: 'relative',
|
|
663
|
+
// For vertical videos: limit width so the preview doesn't get too tall
|
|
664
|
+
// For horizontal videos: use full width
|
|
665
|
+
width: isVertical ? 'auto' : '100%',
|
|
666
|
+
aspectRatio: videoAspectRatio ? String(videoAspectRatio) : '16/9',
|
|
667
|
+
...(isVertical ? {height: '400px', maxHeight: '50vh'} : {minHeight: '200px'}),
|
|
668
|
+
overflow: 'hidden',
|
|
669
|
+
}}
|
|
670
|
+
>
|
|
671
|
+
<video
|
|
672
|
+
ref={videoRef}
|
|
673
|
+
style={{
|
|
674
|
+
position: 'absolute',
|
|
675
|
+
top: 0,
|
|
676
|
+
left: 0,
|
|
677
|
+
width: '100%',
|
|
678
|
+
height: '100%',
|
|
679
|
+
objectFit: 'fill',
|
|
680
|
+
display: 'block',
|
|
681
|
+
}}
|
|
682
|
+
/>
|
|
683
|
+
<DraggableWatermark
|
|
684
|
+
watermark={watermark}
|
|
685
|
+
onChange={onWatermarkChange}
|
|
686
|
+
containerRef={previewContainerRef as React.RefObject<HTMLDivElement>}
|
|
687
|
+
videoElementRef={videoRef as React.RefObject<HTMLVideoElement>}
|
|
688
|
+
/>
|
|
689
|
+
</div>
|
|
690
|
+
</Card>
|
|
691
|
+
)
|
|
692
|
+
})
|
|
@@ -197,9 +197,13 @@ export default function Uploader(props: Props) {
|
|
|
197
197
|
* the Mux configuration for the direct asset upload.
|
|
198
198
|
*
|
|
199
199
|
* @param settings The Mux new_asset_settings object to send to Sanity
|
|
200
|
+
* @param watermark Optional watermark configuration
|
|
200
201
|
* @returns
|
|
201
202
|
*/
|
|
202
|
-
const startUpload = (
|
|
203
|
+
const startUpload = (
|
|
204
|
+
settings: MuxNewAssetSettings,
|
|
205
|
+
watermark?: import('../util/types').WatermarkConfig
|
|
206
|
+
) => {
|
|
203
207
|
const {stagedUpload} = state
|
|
204
208
|
if (!stagedUpload || uploadRef.current) return
|
|
205
209
|
dispatch({action: 'commitUpload'})
|
|
@@ -211,6 +215,7 @@ export default function Uploader(props: Props) {
|
|
|
211
215
|
client: props.client,
|
|
212
216
|
url: stagedUpload.url,
|
|
213
217
|
settings,
|
|
218
|
+
watermark,
|
|
214
219
|
})
|
|
215
220
|
break
|
|
216
221
|
case 'file':
|
|
@@ -218,6 +223,7 @@ export default function Uploader(props: Props) {
|
|
|
218
223
|
client: props.client,
|
|
219
224
|
file: stagedUpload.files[0],
|
|
220
225
|
settings,
|
|
226
|
+
watermark,
|
|
221
227
|
}).pipe(
|
|
222
228
|
takeUntil(
|
|
223
229
|
cancelUploadButton.observable.pipe(
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
ErrorOutlineIcon,
|
|
8
8
|
RevertIcon,
|
|
9
9
|
SearchIcon,
|
|
10
|
+
SyncIcon,
|
|
10
11
|
TagIcon,
|
|
11
12
|
TrashIcon,
|
|
12
13
|
} from '@sanity/icons'
|
|
@@ -71,6 +72,8 @@ const VideoDetails: React.FC<VideoDetailsProps> = (props) => {
|
|
|
71
72
|
handleClose,
|
|
72
73
|
confirmClose,
|
|
73
74
|
saveChanges,
|
|
75
|
+
handleResync,
|
|
76
|
+
isResyncing,
|
|
74
77
|
} = useVideoDetails(props)
|
|
75
78
|
|
|
76
79
|
const isSaving = state === 'saving'
|
|
@@ -97,16 +100,29 @@ const VideoDetails: React.FC<VideoDetailsProps> = (props) => {
|
|
|
97
100
|
footer={
|
|
98
101
|
<Card padding={3}>
|
|
99
102
|
<Flex justify="space-between" align="center">
|
|
100
|
-
<
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
103
|
+
<Flex gap={2}>
|
|
104
|
+
<Button
|
|
105
|
+
icon={TrashIcon}
|
|
106
|
+
fontSize={2}
|
|
107
|
+
padding={3}
|
|
108
|
+
mode="bleed"
|
|
109
|
+
text="Delete"
|
|
110
|
+
tone="critical"
|
|
111
|
+
onClick={() => setState('deleting')}
|
|
112
|
+
disabled={isSaving || isResyncing}
|
|
113
|
+
/>
|
|
114
|
+
<Button
|
|
115
|
+
icon={SyncIcon}
|
|
116
|
+
fontSize={2}
|
|
117
|
+
padding={3}
|
|
118
|
+
mode="bleed"
|
|
119
|
+
text="Resync"
|
|
120
|
+
tone="primary"
|
|
121
|
+
onClick={handleResync}
|
|
122
|
+
disabled={isSaving || isResyncing}
|
|
123
|
+
iconRight={isResyncing && Spinner}
|
|
124
|
+
/>
|
|
125
|
+
</Flex>
|
|
110
126
|
{modified && (
|
|
111
127
|
<Button
|
|
112
128
|
icon={CheckmarkIcon}
|
|
@@ -117,7 +133,7 @@ const VideoDetails: React.FC<VideoDetailsProps> = (props) => {
|
|
|
117
133
|
tone="positive"
|
|
118
134
|
onClick={saveChanges}
|
|
119
135
|
iconRight={isSaving && Spinner}
|
|
120
|
-
disabled={isSaving}
|
|
136
|
+
disabled={isSaving || isResyncing}
|
|
121
137
|
/>
|
|
122
138
|
)}
|
|
123
139
|
</Flex>
|
|
@@ -4,9 +4,12 @@ import {useDocumentStore} from 'sanity'
|
|
|
4
4
|
|
|
5
5
|
import {useClient} from '../../hooks/useClient'
|
|
6
6
|
import useDocReferences from '../../hooks/useDocReferences'
|
|
7
|
+
import {useResyncAsset} from '../../hooks/useResyncAsset'
|
|
7
8
|
import getVideoMetadata from '../../util/getVideoMetadata'
|
|
8
9
|
import {VideoAssetDocument} from '../../util/types'
|
|
9
10
|
|
|
11
|
+
type VideoDetailsState = 'idle' | 'saving' | 'deleting' | 'closing' | 'resyncing'
|
|
12
|
+
|
|
10
13
|
export interface VideoDetailsProps {
|
|
11
14
|
closeDialog: () => void
|
|
12
15
|
asset: VideoAssetDocument & {autoPlay?: boolean}
|
|
@@ -27,7 +30,16 @@ export default function useVideoDetails(props: VideoDetailsProps) {
|
|
|
27
30
|
|
|
28
31
|
const displayInfo = getVideoMetadata({...props.asset, filename})
|
|
29
32
|
|
|
30
|
-
const [state, setState] = useState<
|
|
33
|
+
const [state, setState] = useState<VideoDetailsState>('idle')
|
|
34
|
+
|
|
35
|
+
const {resyncAsset, isResyncing} = useResyncAsset({showToast: true})
|
|
36
|
+
|
|
37
|
+
async function handleResync() {
|
|
38
|
+
if (state !== 'idle') return
|
|
39
|
+
setState('resyncing')
|
|
40
|
+
await resyncAsset(props.asset)
|
|
41
|
+
setState('idle')
|
|
42
|
+
}
|
|
31
43
|
|
|
32
44
|
function handleClose() {
|
|
33
45
|
if (state !== 'idle') return
|
|
@@ -85,5 +97,7 @@ export default function useVideoDetails(props: VideoDetailsProps) {
|
|
|
85
97
|
handleClose,
|
|
86
98
|
confirmClose,
|
|
87
99
|
saveChanges,
|
|
100
|
+
handleResync,
|
|
101
|
+
isResyncing,
|
|
88
102
|
}
|
|
89
103
|
}
|
|
@@ -37,6 +37,7 @@ export default function VideoPlayer({
|
|
|
37
37
|
|
|
38
38
|
const isAudio = assetIsAudio(asset)
|
|
39
39
|
const muxPlayer = useRef<MuxPlayerRefAttributes>(null)
|
|
40
|
+
const playerContainerRef = useRef<HTMLDivElement>(null)
|
|
40
41
|
const [error, setError] = useState<Error>()
|
|
41
42
|
|
|
42
43
|
/* Playback ID that will be used to play the video */
|
|
@@ -149,6 +150,7 @@ export default function VideoPlayer({
|
|
|
149
150
|
return (
|
|
150
151
|
<>
|
|
151
152
|
<Card
|
|
153
|
+
ref={playerContainerRef}
|
|
152
154
|
tone="transparent"
|
|
153
155
|
style={{
|
|
154
156
|
aspectRatio: aspectRatio,
|
|
@@ -8,6 +8,7 @@ export interface VideoAssetMetadata {
|
|
|
8
8
|
isAudioOnly?: boolean
|
|
9
9
|
duration?: number
|
|
10
10
|
size?: number
|
|
11
|
+
aspectRatio?: number
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export function useMediaMetadata(stagedUpload: StagedUpload) {
|
|
@@ -48,6 +49,7 @@ export function useMediaMetadata(stagedUpload: StagedUpload) {
|
|
|
48
49
|
const width = videoElement.videoWidth
|
|
49
50
|
const height = videoElement.videoHeight
|
|
50
51
|
const isAudioOnly = width <= 0 && height <= 0
|
|
52
|
+
const aspectRatio = width / height
|
|
51
53
|
setVideoAssetMetadata((old) => {
|
|
52
54
|
return {
|
|
53
55
|
...old,
|
|
@@ -55,6 +57,7 @@ export function useMediaMetadata(stagedUpload: StagedUpload) {
|
|
|
55
57
|
width: width,
|
|
56
58
|
height: height,
|
|
57
59
|
isAudioOnly: isAudioOnly,
|
|
60
|
+
aspectRatio: aspectRatio,
|
|
58
61
|
}
|
|
59
62
|
})
|
|
60
63
|
},
|