@linktr.ee/messaging-react 1.31.0 → 1.32.1-rc-1777007852

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 (33) hide show
  1. package/dist/Card-1CQEn-OT.js +171 -0
  2. package/dist/Card-1CQEn-OT.js.map +1 -0
  3. package/dist/Card-ClE_iExA.js +177 -0
  4. package/dist/Card-ClE_iExA.js.map +1 -0
  5. package/dist/{MediaPlayer-BCsdmsON.js → MediaPlayer-B9Ws2NeE.js} +115 -135
  6. package/dist/MediaPlayer-B9Ws2NeE.js.map +1 -0
  7. package/dist/index.d.ts +3 -2
  8. package/dist/index.js +79 -63
  9. package/dist/index.js.map +1 -1
  10. package/package.json +1 -1
  11. package/src/components/ChannelView.stories.tsx +38 -7
  12. package/src/components/CustomMessageInput/CustomMessageInput.test.tsx +127 -0
  13. package/src/components/CustomMessageInput/index.tsx +25 -8
  14. package/src/components/LockedAttachment/LockedAttachment.stories.tsx +136 -93
  15. package/src/components/LockedAttachment/components/Creator/Card.tsx +106 -106
  16. package/src/components/LockedAttachment/components/Creator/CardThumbnail.tsx +114 -0
  17. package/src/components/LockedAttachment/components/MediaPlayer.tsx +80 -66
  18. package/src/components/LockedAttachment/components/Visitor/Card.tsx +53 -78
  19. package/src/components/LockedAttachment/components/Visitor/CardActions.tsx +3 -3
  20. package/src/components/LockedAttachment/components/Visitor/CardThumbnail.tsx +81 -0
  21. package/src/components/LockedAttachment/types.ts +2 -0
  22. package/dist/Card-C5t3dZ5q.js +0 -350
  23. package/dist/Card-C5t3dZ5q.js.map +0 -1
  24. package/dist/Card-Cn2va-Qr.js +0 -205
  25. package/dist/Card-Cn2va-Qr.js.map +0 -1
  26. package/dist/MediaPlayer-BCsdmsON.js.map +0 -1
  27. package/src/components/LockedAttachment/components/Creator/CardAudioPreview.tsx +0 -161
  28. package/src/components/LockedAttachment/components/Creator/CardCollapsedThumbnail.tsx +0 -58
  29. package/src/components/LockedAttachment/components/Creator/CardImagePreview.tsx +0 -56
  30. package/src/components/LockedAttachment/components/Creator/CardVideoPreview.tsx +0 -91
  31. package/src/components/LockedAttachment/components/Visitor/CardImagePreview.tsx +0 -39
  32. package/src/components/LockedAttachment/components/Visitor/CardMediaPreview.tsx +0 -36
  33. package/src/components/LockedAttachment/components/Visitor/CardThumbnailPreview.tsx +0 -45
@@ -1,105 +1,72 @@
1
1
  import {
2
2
  CheckCircleIcon,
3
+ EyeIcon,
4
+ EyeSlashIcon,
3
5
  LockIcon,
4
6
  LockOpenIcon,
5
7
  XIcon,
6
8
  } from '@phosphor-icons/react'
7
9
  import classNames from 'classnames'
8
- import React from 'react'
10
+ import React, { useCallback, useState } from 'react'
9
11
 
10
- import type { LockedAttachmentBaseProps } from '../../types'
12
+ import type {
13
+ LockedAttachmentBaseProps,
14
+ LockedAttachmentSource,
15
+ PaymentStatus,
16
+ } from '../../types'
11
17
  import { renderTypeIcon } from '../../utils/icons'
12
- import { getSourceType } from '../../utils/mimeType'
13
18
 
14
- import AudioPreview from './CardAudioPreview'
15
- import CollapsedThumbnail from './CardCollapsedThumbnail'
16
- import ImagePreview from './CardImagePreview'
17
- import VideoPreview from './CardVideoPreview'
19
+ import CardThumbnail from './CardThumbnail'
18
20
 
19
21
  export interface CreatorCardProps extends LockedAttachmentBaseProps {
20
- isPreview?: boolean
21
22
  placeholderTitle?: string
22
23
  placeholderAmountText?: string
23
- sourceUrl?: string
24
24
  onDismiss?: () => void
25
+ onPreviewClick?: () => LockedAttachmentSource
25
26
  }
26
27
 
27
- const CreatorCard: React.FC<CreatorCardProps> = (props) => {
28
- const {
29
- title,
30
- mimeType = 'application/octet-stream',
31
- thumbnailUrl,
32
- sourceUrl,
33
- detail,
34
- amountText,
35
- placeholderTitle = 'Attachment title',
36
- placeholderAmountText,
37
- paymentStatus,
38
- onDismiss,
39
- isPreview = false,
40
- } = props
41
- const sourceType = getSourceType(mimeType)
42
- const displayAmountText = amountText ?? placeholderAmountText
43
- const isPlaceholderAmount = !amountText && !!placeholderAmountText
44
-
45
- let mediaPreview: React.ReactNode
46
- if (isPreview && sourceType === 'audio') {
47
- mediaPreview = (
48
- <AudioPreview
49
- key={sourceUrl}
50
- sourceUrl={sourceUrl}
51
- thumbnailUrl={thumbnailUrl}
52
- mimeType={mimeType}
53
- />
54
- )
55
- } else if (isPreview && sourceType === 'video') {
56
- mediaPreview = (
57
- <VideoPreview
58
- key={sourceUrl}
59
- sourceUrl={sourceUrl}
60
- thumbnailUrl={thumbnailUrl}
61
- mimeType={mimeType}
28
+ const CreatorCard: React.FC<CreatorCardProps> = ({
29
+ title,
30
+ mimeType = 'application/octet-stream',
31
+ thumbnailUrl,
32
+ detail,
33
+ amountText,
34
+ placeholderTitle = 'Attachment title',
35
+ placeholderAmountText,
36
+ paymentStatus,
37
+ onDismiss,
38
+ onPreviewClick,
39
+ }) => {
40
+ const [source, setSource] = useState<LockedAttachmentSource | undefined>()
41
+
42
+ const effectiveSourceUrl = source?.sourceUrl
43
+ const effectiveThumbnailUrl = source?.thumbnailUrl ?? thumbnailUrl
44
+
45
+ const handleToggle = useCallback(() => {
46
+ if (source) {
47
+ setSource(undefined)
48
+ } else if (onPreviewClick) {
49
+ setSource(onPreviewClick())
50
+ }
51
+ }, [source, onPreviewClick])
52
+
53
+ return (
54
+ <div className="relative w-[280px] select-none overflow-hidden rounded-[24px] bg-white shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_4px_8px_rgba(0,0,0,0.06)]">
55
+ <CardHeader
56
+ onDismiss={onDismiss}
57
+ onPreviewClick={onPreviewClick}
58
+ sourceUrl={source?.sourceUrl}
59
+ paymentStatus={paymentStatus}
62
60
  />
63
- )
64
- } else if (isPreview && sourceType === 'image') {
65
- mediaPreview = (
66
- <ImagePreview
67
- key={sourceUrl}
68
- sourceUrl={sourceUrl}
69
- thumbnailUrl={thumbnailUrl}
70
- mimeType={mimeType}
61
+
62
+ <CardThumbnail
71
63
  title={title}
72
- />
73
- )
74
- } else {
75
- const lockedOverlayIcon = onDismiss
76
- ? undefined
77
- : paymentStatus === 'paid'
78
- ? LockOpenIcon
79
- : LockIcon
80
- mediaPreview = (
81
- <CollapsedThumbnail
82
- thumbnailUrl={thumbnailUrl}
64
+ sourceUrl={effectiveSourceUrl}
65
+ thumbnailUrl={effectiveThumbnailUrl}
83
66
  mimeType={mimeType}
84
- overlayIcon={lockedOverlayIcon}
85
- darkOverlay
67
+ onToggle={onPreviewClick ? handleToggle : undefined}
86
68
  />
87
- )
88
- }
89
69
 
90
- return (
91
- <div className="relative w-[280px] select-none overflow-hidden rounded-[24px] bg-white shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_4px_8px_rgba(0,0,0,0.06)]">
92
- {onDismiss && (
93
- <button
94
- type="button"
95
- onClick={onDismiss}
96
- className="absolute right-3 top-3 z-50 flex size-8 items-center justify-center rounded-full bg-black/60 text-white"
97
- aria-label="Dismiss attachment"
98
- >
99
- <XIcon className="size-4" weight="bold" />
100
- </button>
101
- )}
102
- {mediaPreview}
103
70
  <div className="px-4 pb-3 pt-3">
104
71
  <p
105
72
  className={classNames('mb-1.5 truncate text-base font-medium', {
@@ -109,46 +76,38 @@ const CreatorCard: React.FC<CreatorCardProps> = (props) => {
109
76
  >
110
77
  {title || placeholderTitle}
111
78
  </p>
79
+
112
80
  <div className="flex items-center gap-1">
113
81
  {renderTypeIcon(mimeType, {
114
82
  className: 'size-5 shrink-0 text-black/55',
115
83
  weight: 'regular',
116
84
  })}
85
+
117
86
  {detail && (
118
87
  <span className="text-xs font-medium text-black/55">{detail}</span>
119
88
  )}
89
+
120
90
  {paymentStatus === 'paid' ? (
121
- <>
122
- <span className="text-xs font-medium text-black/55">•</span>
123
- <span className="text-xs font-medium text-[#008236]">
124
- Purchased
125
- </span>
91
+ <React.Fragment>
92
+ <span className="text-xs font-medium text-black/55">&bull;</span>
93
+ <span className="text-xs font-medium text-[#008236]">Sold</span>
126
94
  <CheckCircleIcon
127
95
  className="size-4 text-[#008236]"
128
96
  weight="bold"
129
97
  />
130
- </>
98
+ </React.Fragment>
131
99
  ) : (
132
- displayAmountText && (
133
- <>
134
- <span
135
- className={classNames('text-xs font-medium', {
136
- 'text-black/30': isPlaceholderAmount,
137
- 'text-black/55': !isPlaceholderAmount,
138
- })}
139
- >
140
-
141
- </span>
142
- <span
143
- className={classNames('text-xs font-medium', {
144
- 'text-black/30': isPlaceholderAmount,
145
- 'text-black/55': !isPlaceholderAmount,
146
- })}
147
- >
148
- {displayAmountText}
149
- </span>
150
- </>
151
- )
100
+ <React.Fragment>
101
+ <span className="text-xs font-medium text-black/55">&bull;</span>
102
+ <span
103
+ className={classNames('text-xs font-medium', {
104
+ 'text-black/30': !amountText,
105
+ 'text-black/55': !!amountText,
106
+ })}
107
+ >
108
+ {amountText || placeholderAmountText}
109
+ </span>
110
+ </React.Fragment>
152
111
  )}
153
112
  </div>
154
113
  </div>
@@ -156,4 +115,45 @@ const CreatorCard: React.FC<CreatorCardProps> = (props) => {
156
115
  )
157
116
  }
158
117
 
118
+ interface CardHeaderProps {
119
+ onDismiss?: () => void
120
+ onPreviewClick?: () => void
121
+ sourceUrl?: string
122
+ paymentStatus?: PaymentStatus
123
+ }
124
+
125
+ const CardHeader: React.FC<CardHeaderProps> = ({
126
+ onDismiss,
127
+ onPreviewClick,
128
+ sourceUrl,
129
+ paymentStatus,
130
+ }) => {
131
+ if (onDismiss) {
132
+ return (
133
+ <button
134
+ type="button"
135
+ onClick={onDismiss}
136
+ className="absolute top-3 z-50 flex size-8 items-center justify-center rounded-full bg-black/60 text-white right-3"
137
+ aria-label="Dismiss attachment"
138
+ >
139
+ <XIcon className="size-4" weight="bold" />
140
+ </button>
141
+ )
142
+ }
143
+
144
+ const Icon = onPreviewClick
145
+ ? sourceUrl
146
+ ? EyeIcon
147
+ : EyeSlashIcon
148
+ : paymentStatus === 'paid'
149
+ ? LockOpenIcon
150
+ : LockIcon
151
+
152
+ return (
153
+ <div className="absolute top-3 z-50 flex size-8 items-center justify-center rounded-full bg-black/60 text-white left-3">
154
+ <Icon className="size-4" weight="fill" />
155
+ </div>
156
+ )
157
+ }
158
+
159
159
  export default CreatorCard
@@ -0,0 +1,114 @@
1
+ import classNames from 'classnames'
2
+ import React from 'react'
3
+
4
+ import { renderTypeIcon } from '../../utils/icons'
5
+ import { getSourceType } from '../../utils/mimeType'
6
+ import MediaPlayer from '../MediaPlayer'
7
+
8
+ interface CardThumbnailProps {
9
+ title?: string
10
+ sourceUrl?: string
11
+ thumbnailUrl?: string
12
+ mimeType: string
13
+ onToggle?: () => void
14
+ }
15
+
16
+ const CardThumbnail: React.FC<CardThumbnailProps> = ({
17
+ title,
18
+ sourceUrl,
19
+ thumbnailUrl,
20
+ mimeType,
21
+ onToggle,
22
+ }) => {
23
+ const isExpanded = onToggle && sourceUrl && thumbnailUrl
24
+
25
+ return (
26
+ <button
27
+ type="button"
28
+ disabled={!onToggle}
29
+ className={classNames(
30
+ 'relative block w-full overflow-hidden border-0 bg-black/5 p-0 text-left appearance-none',
31
+ { 'cursor-pointer': !!onToggle, 'cursor-default': !onToggle }
32
+ )}
33
+ onClick={onToggle}
34
+ aria-label={onToggle ? 'Toggle preview' : undefined}
35
+ >
36
+ {isExpanded ? (
37
+ <ThumbnailMedia
38
+ sourceUrl={sourceUrl}
39
+ thumbnailUrl={thumbnailUrl}
40
+ mimeType={mimeType}
41
+ />
42
+ ) : thumbnailUrl ? (
43
+ <div className="aspect-video overflow-hidden">
44
+ <img
45
+ src={thumbnailUrl}
46
+ alt={title}
47
+ draggable={false}
48
+ className="absolute inset-0 h-full w-full object-cover"
49
+ />
50
+ </div>
51
+ ) : (
52
+ <div className="aspect-video flex items-center justify-center">
53
+ {renderTypeIcon(mimeType, {
54
+ className: 'size-12 text-black/20',
55
+ weight: 'regular',
56
+ })}
57
+ </div>
58
+ )}
59
+
60
+ {!isExpanded && (
61
+ <div className="pointer-events-none absolute inset-0 bg-black/30" />
62
+ )}
63
+ </button>
64
+ )
65
+ }
66
+
67
+ interface ThumbnailMediaProps {
68
+ sourceUrl: string
69
+ thumbnailUrl: string
70
+ mimeType: string
71
+ }
72
+
73
+ const ThumbnailMedia: React.FC<ThumbnailMediaProps> = ({
74
+ sourceUrl,
75
+ thumbnailUrl,
76
+ mimeType,
77
+ }) => {
78
+ const sourceType = getSourceType(mimeType)
79
+
80
+ if (sourceType === 'video' || sourceType === 'audio') {
81
+ return (
82
+ <MediaPlayer
83
+ mimeType={mimeType}
84
+ source={sourceUrl}
85
+ poster={thumbnailUrl}
86
+ autoPlay={true}
87
+ loop={true}
88
+ controls={true}
89
+ muted={false}
90
+ />
91
+ )
92
+ }
93
+
94
+ if (sourceType === 'image') {
95
+ return (
96
+ <img src={sourceUrl} alt="" className="block w-full" draggable={false} />
97
+ )
98
+ }
99
+
100
+ if (sourceType === 'document') {
101
+ return (
102
+ <img
103
+ src={thumbnailUrl}
104
+ alt=""
105
+ className="block w-full"
106
+ draggable={false}
107
+ />
108
+ )
109
+ }
110
+
111
+ return null
112
+ }
113
+
114
+ export default CardThumbnail
@@ -3,13 +3,16 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'
3
3
 
4
4
  import { isDevBuild } from '../../../utils/isDevBuild'
5
5
  import { renderTypeIcon } from '../utils/icons'
6
- import { getSourceType, type AttachmentSourceType } from '../utils/mimeType'
6
+ import { getSourceType } from '../utils/mimeType'
7
+
8
+ type TouchEventUnion =
9
+ | MouseEvent
10
+ | TouchEvent
11
+ | React.MouseEvent
12
+ | React.TouchEvent
7
13
 
8
- const getPlayerBg = (sourceType: AttachmentSourceType, poster?: string) => {
9
- return sourceType === 'audio' && !poster ? 'bg-black/5' : 'bg-black'
10
- }
11
14
 
12
- const getClientXFromEvent = ( e: MouseEvent | TouchEvent | React.MouseEvent | React.TouchEvent ): number => {
15
+ const getClientXFromEvent = (e: TouchEventUnion): number => {
13
16
  if ('touches' in e) {
14
17
  return e.touches[0]?.clientX ?? e.changedTouches[0]?.clientX ?? 0
15
18
  }
@@ -26,7 +29,6 @@ export interface MediaPlayerProps {
26
29
  loop?: boolean
27
30
  controls?: boolean
28
31
  showProgress?: boolean
29
- onContainerClick?: () => void
30
32
  /** When true, requests muted playback (helps autoplay policies on video). */
31
33
  muted?: boolean
32
34
  }
@@ -40,14 +42,67 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
40
42
  loop = false,
41
43
  controls = true,
42
44
  showProgress = false,
43
- onContainerClick,
44
45
  muted = false,
45
46
  }) => {
47
+ // --- Derived ---
46
48
  const sourceType = getSourceType(mimeType)
49
+
50
+ // --- Refs ---
51
+ const playerRef = useRef<HTMLMediaElement>(null)
52
+ const trackRef = useRef<HTMLDivElement>(null)
53
+ const rafRef = useRef<number | null>(null)
54
+ const prevPlayingPropRef = useRef(playingProp)
55
+
56
+ // --- State: playback ---
47
57
  const [playing, setPlaying] = useState(autoPlay)
58
+ const [played, setPlayed] = useState(0)
59
+ const [seeking, setSeeking] = useState(false)
60
+
61
+ // --- State: UI ---
62
+ const [scrubberHovered, setScrubberHovered] = useState(false)
63
+ /** Set when autoplay/play() was rejected so user can start via gesture (no controls UI). */
64
+ const [manualPlayRequired, setManualPlayRequired] = useState(false)
65
+
66
+ // --- State: loading ---
67
+ const [buffering, setBuffering] = useState(false)
68
+ /** True until the first canPlay fires for the current source — hides controls/spinner behind poster. */
69
+ const [initialLoad, setInitialLoad] = useState(true)
70
+ const [videoAspect, setVideoAspect] = useState<number | null>(null)
71
+
72
+ // --- Callbacks ---
73
+ const startPlaybackFromGesture = useCallback(() => {
74
+ setManualPlayRequired(false)
75
+ setPlaying(true)
76
+ }, [])
77
+
78
+ const getFraction = useCallback((e: TouchEventUnion) => {
79
+ const track = trackRef.current
80
+ if (!track) return 0
81
+ const rect = track.getBoundingClientRect()
82
+ return Math.max(
83
+ 0,
84
+ Math.min(1, (getClientXFromEvent(e) - rect.left) / rect.width)
85
+ )
86
+ }, [])
87
+
88
+ const seekTo = useCallback((fraction: number) => {
89
+ const el = playerRef.current
90
+ if (el && el.duration) el.currentTime = fraction * el.duration
91
+ }, [])
92
+
93
+ const handleTrackPointerDown = (
94
+ e: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>
95
+ ) => {
96
+ e.stopPropagation()
97
+ setSeeking(true)
98
+ const fraction = getFraction(e)
99
+ setPlayed(fraction)
100
+ seekTo(fraction)
101
+ }
102
+
103
+ // --- Effects ---
48
104
 
49
105
  // Sync controlled playing prop to internal state
50
- const prevPlayingPropRef = useRef(playingProp)
51
106
  useEffect(() => {
52
107
  if (
53
108
  playingProp !== undefined &&
@@ -57,20 +112,8 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
57
112
  setPlaying(playingProp)
58
113
  }
59
114
  }, [playingProp])
60
- const [played, setPlayed] = useState(0)
61
- const [seeking, setSeeking] = useState(false)
62
- const [scrubberHovered, setScrubberHovered] = useState(false)
63
- const [videoAspect, setVideoAspect] = useState<number | null>(null)
64
- const [buffering, setBuffering] = useState(false)
65
- /** True until the first canPlay fires for the current source — hides controls/spinner behind poster. */
66
- const [initialLoad, setInitialLoad] = useState(true)
67
- /** Set when autoplay/play() was rejected so user can start via gesture (no controls UI). */
68
- const [manualPlayRequired, setManualPlayRequired] = useState(false)
69
- const playerRef = useRef<HTMLMediaElement>(null)
70
- const trackRef = useRef<HTMLDivElement>(null)
71
- const rafRef = useRef<number | null>(null)
72
-
73
115
 
116
+ // RAF-driven progress updates
74
117
  useEffect(() => {
75
118
  if (!playing) {
76
119
  if (rafRef.current !== null) {
@@ -108,39 +151,7 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
108
151
  }
109
152
  }, [playing])
110
153
 
111
- const startPlaybackFromGesture = useCallback(() => {
112
- setManualPlayRequired(false)
113
- setPlaying(true)
114
- }, [])
115
-
116
- const getFraction = useCallback(
117
- (e: MouseEvent | TouchEvent | React.MouseEvent | React.TouchEvent) => {
118
- const track = trackRef.current
119
- if (!track) return 0
120
- const rect = track.getBoundingClientRect()
121
- return Math.max(
122
- 0,
123
- Math.min(1, (getClientXFromEvent(e) - rect.left) / rect.width)
124
- )
125
- },
126
- []
127
- )
128
-
129
- const seekTo = useCallback((fraction: number) => {
130
- const el = playerRef.current
131
- if (el && el.duration) el.currentTime = fraction * el.duration
132
- }, [])
133
-
134
- const handleTrackPointerDown = (
135
- e: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>
136
- ) => {
137
- e.stopPropagation()
138
- setSeeking(true)
139
- const fraction = getFraction(e)
140
- setPlayed(fraction)
141
- seekTo(fraction)
142
- }
143
-
154
+ // Global seeking listeners
144
155
  useEffect(() => {
145
156
  if (!seeking) return
146
157
  const onMove = (e: MouseEvent | TouchEvent) => setPlayed(getFraction(e))
@@ -160,6 +171,7 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
160
171
  }
161
172
  }, [seeking, getFraction, seekTo])
162
173
 
174
+ // --- Derived render values ---
163
175
  // Use natural aspect ratio once metadata loads, fall back to 16:9 before then.
164
176
  const aspectStyle = videoAspect
165
177
  ? { aspectRatio: String(videoAspect) }
@@ -171,24 +183,16 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
171
183
  <div
172
184
  role="button"
173
185
  tabIndex={0}
174
- className={`relative cursor-pointer overflow-hidden ${getPlayerBg(sourceType, poster)}${aspectClass}`}
186
+ className={`relative cursor-pointer overflow-hidden bg-black ${aspectClass}`}
175
187
  style={aspectStyle}
176
188
  onClick={() => {
177
189
  if (manualPlayRequired) return
178
- if (onContainerClick) {
179
- onContainerClick()
180
- return
181
- }
182
190
  if (controls) setPlaying((p) => !p)
183
191
  }}
184
192
  onKeyDown={(e) => {
185
193
  if (e.key !== 'Enter' && e.key !== ' ') return
186
194
  e.preventDefault()
187
195
  if (manualPlayRequired) return
188
- if (onContainerClick) {
189
- onContainerClick()
190
- return
191
- }
192
196
  if (controls) setPlaying((p) => !p)
193
197
  }}
194
198
  >
@@ -217,7 +221,10 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
217
221
  muted={muted}
218
222
  style={{ width: '100%', height: '100%' }}
219
223
  onLoadStart={() => setBuffering(true)}
220
- onCanPlay={() => { setBuffering(false); setInitialLoad(false) }}
224
+ onCanPlay={() => {
225
+ setBuffering(false)
226
+ setInitialLoad(false)
227
+ }}
221
228
  onWaiting={() => setBuffering(true)}
222
229
  onPlay={() => setManualPlayRequired(false)}
223
230
  onEnded={() => {
@@ -238,12 +245,19 @@ const MediaPlayer: React.FC<MediaPlayerProps> = ({
238
245
  playsInline
239
246
  style={{ width: '100%', height: '100%' }}
240
247
  onLoadStart={() => setBuffering(true)}
241
- onCanPlay={() => { setBuffering(false); setInitialLoad(false) }}
248
+ onCanPlay={() => {
249
+ setBuffering(false)
250
+ setInitialLoad(false)
251
+ }}
242
252
  onWaiting={() => setBuffering(true)}
243
253
  onPlay={() => setManualPlayRequired(false)}
244
254
  onLoadedMetadata={() => {
245
255
  const el = playerRef.current
246
- if (el instanceof HTMLVideoElement && el.videoWidth && el.videoHeight) {
256
+ if (
257
+ el instanceof HTMLVideoElement &&
258
+ el.videoWidth &&
259
+ el.videoHeight
260
+ ) {
247
261
  setVideoAspect(el.videoWidth / el.videoHeight)
248
262
  }
249
263
  }}