@hh.ru/magritte-ui-video 1.0.1 → 1.0.2

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/Video.js CHANGED
@@ -13,7 +13,7 @@ import '@hh.ru/magritte-ui-icon/icon';
13
13
  import '@hh.ru/magritte-ui-typography';
14
14
  import './internal/ControlButton.js';
15
15
  import '@hh.ru/magritte-ui-tooltip';
16
- import './video-C-iklAZX.js';
16
+ import './video-BnfN4Emx.js';
17
17
  import './internal/Scrubber.js';
18
18
  import './internal/formatTime.js';
19
19
  import './internal/useSliderPointer.js';
@@ -29,12 +29,44 @@ import '@hh.ru/magritte-ui-loader';
29
29
  import '@hh.ru/magritte-ui-placeholder';
30
30
  import './internal/SeekIndicator.js';
31
31
  import './internal/useCanvasGestures.js';
32
+ import './internal/useViewportVisibility.js';
32
33
 
33
34
  const Video = forwardRef(
34
- ({ src, sources, preview, duration, attachMedia, onPlay, onPause, onEnded, onError, children }, ref) => {
35
+ ({
36
+ src,
37
+ sources,
38
+ preview,
39
+ duration,
40
+ attachMedia,
41
+ autoPlay = false,
42
+ autoPause = false,
43
+ loop = false,
44
+ defaultMuted,
45
+ defaultVolume,
46
+ interactive = true,
47
+ onPlay,
48
+ onPause,
49
+ onEnded,
50
+ onError,
51
+ children
52
+ }, ref) => {
35
53
  const callbacksRef = useRef({ attachMedia, onPlay, onPause, onEnded, onError });
36
54
  callbacksRef.current = { attachMedia, onPlay, onPause, onEnded, onError };
37
- const [store] = useState(() => createVideoStore({ src, sources, getCallbacks: () => callbacksRef.current }));
55
+ const optionsRef = useRef({ autoPlay, autoPause, loop, interactive });
56
+ optionsRef.current = { autoPlay, autoPause, loop, interactive };
57
+ const [store] = useState(
58
+ () => createVideoStore({
59
+ src,
60
+ sources,
61
+ defaultMuted,
62
+ defaultVolume,
63
+ getCallbacks: () => callbacksRef.current,
64
+ getOptions: () => optionsRef.current
65
+ })
66
+ );
67
+ useEffect(() => {
68
+ store.syncOptions();
69
+ }, [store, autoPlay, autoPause, loop, interactive]);
38
70
  useEffect(() => {
39
71
  store.setSource({ src, sources });
40
72
  }, [store, src, sources]);
@@ -49,8 +81,15 @@ const Video = forwardRef(
49
81
  [store]
50
82
  );
51
83
  const contextValue = useMemo(
52
- () => ({ store, preview, duration }),
53
- [store, preview, duration]
84
+ () => ({
85
+ store,
86
+ preview,
87
+ duration,
88
+ interactive,
89
+ // плееру нужна видимость во вьюпорте: для автовоспроизведения и автопаузы
90
+ observeViewport: autoPlay || autoPause || !interactive
91
+ }),
92
+ [store, preview, duration, interactive, autoPlay, autoPause]
54
93
  );
55
94
  return /* @__PURE__ */ jsx(VideoContext.Provider, { value: contextValue, children });
56
95
  }
package/Video.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Video.js","sources":["src/Video.tsx"],"sourcesContent":["import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';\n\nimport { VideoControls } from '@hh.ru/magritte-ui-video/VideoControls';\nimport { VideoPlayer } from '@hh.ru/magritte-ui-video/VideoPlayer';\nimport { VideoContext, type VideoContextValue } from '@hh.ru/magritte-ui-video/internal/context';\nimport { createVideoStore } from '@hh.ru/magritte-ui-video/internal/store';\nimport { type VideoHandle, type VideoProps } from '@hh.ru/magritte-ui-video/types';\n\ninterface VideoComponents {\n Player: typeof VideoPlayer;\n Controls: typeof VideoControls;\n}\n\n/**\n * Корень видео: хранит состояние воспроизведения и раздаёт его частям (`Video.Player`, `Video.Controls`).\n * Собственной разметки не создаёт\n */\nexport const Video = forwardRef<VideoHandle, VideoProps>(\n ({ src, sources, preview, duration, attachMedia, onPlay, onPause, onEnded, onError, children }, ref) => {\n const callbacksRef = useRef({ attachMedia, onPlay, onPause, onEnded, onError });\n callbacksRef.current = { attachMedia, onPlay, onPause, onEnded, onError };\n\n const [store] = useState(() => createVideoStore({ src, sources, getCallbacks: () => callbacksRef.current }));\n\n // стор сравнивает источники по значению — новый массив sources на каждый рендер не перезагружает видео\n useEffect(() => {\n store.setSource({ src, sources });\n }, [store, src, sources]);\n\n useImperativeHandle(\n ref,\n () => ({\n play: store.play,\n pause: store.pause,\n seek: store.seek,\n getVideoElement: store.getVideoElement,\n }),\n [store]\n );\n\n const contextValue = useMemo<VideoContextValue>(\n () => ({ store, preview, duration }),\n [store, preview, duration]\n );\n\n return <VideoContext.Provider value={contextValue}>{children}</VideoContext.Provider>;\n }\n) as React.ForwardRefExoticComponent<VideoProps & React.RefAttributes<VideoHandle>> & VideoComponents;\n\nVideo.displayName = 'Video';\n\nVideo.Player = VideoPlayer;\nVideo.Controls = VideoControls;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiBO,MAAM,KAAQ,GAAA,UAAA;AAAA,EACjB,CAAC,EAAE,GAAK,EAAA,OAAA,EAAS,OAAS,EAAA,QAAA,EAAU,WAAa,EAAA,MAAA,EAAQ,OAAS,EAAA,OAAA,EAAS,OAAS,EAAA,QAAA,IAAY,GAAQ,KAAA;AACpG,IAAM,MAAA,YAAA,GAAe,OAAO,EAAE,WAAA,EAAa,QAAQ,OAAS,EAAA,OAAA,EAAS,SAAS,CAAA,CAAA;AAC9E,IAAA,YAAA,CAAa,UAAU,EAAE,WAAA,EAAa,MAAQ,EAAA,OAAA,EAAS,SAAS,OAAQ,EAAA,CAAA;AAExE,IAAA,MAAM,CAAC,KAAK,CAAI,GAAA,QAAA,CAAS,MAAM,gBAAiB,CAAA,EAAE,GAAK,EAAA,OAAA,EAAS,YAAc,EAAA,MAAM,YAAa,CAAA,OAAA,EAAS,CAAC,CAAA,CAAA;AAG3G,IAAA,SAAA,CAAU,MAAM;AACZ,MAAA,KAAA,CAAM,SAAU,CAAA,EAAE,GAAK,EAAA,OAAA,EAAS,CAAA,CAAA;AAAA,KACjC,EAAA,CAAC,KAAO,EAAA,GAAA,EAAK,OAAO,CAAC,CAAA,CAAA;AAExB,IAAA,mBAAA;AAAA,MACI,GAAA;AAAA,MACA,OAAO;AAAA,QACH,MAAM,KAAM,CAAA,IAAA;AAAA,QACZ,OAAO,KAAM,CAAA,KAAA;AAAA,QACb,MAAM,KAAM,CAAA,IAAA;AAAA,QACZ,iBAAiB,KAAM,CAAA,eAAA;AAAA,OAC3B,CAAA;AAAA,MACA,CAAC,KAAK,CAAA;AAAA,KACV,CAAA;AAEA,IAAA,MAAM,YAAe,GAAA,OAAA;AAAA,MACjB,OAAO,EAAE,KAAO,EAAA,OAAA,EAAS,QAAS,EAAA,CAAA;AAAA,MAClC,CAAC,KAAO,EAAA,OAAA,EAAS,QAAQ,CAAA;AAAA,KAC7B,CAAA;AAEA,IAAA,2BAAQ,YAAa,CAAA,QAAA,EAAb,EAAsB,KAAA,EAAO,cAAe,QAAS,EAAA,CAAA,CAAA;AAAA,GACjE;AACJ,EAAA;AAEA,KAAA,CAAM,WAAc,GAAA,OAAA,CAAA;AAEpB,KAAA,CAAM,MAAS,GAAA,WAAA,CAAA;AACf,KAAA,CAAM,QAAW,GAAA,aAAA;;;;"}
1
+ {"version":3,"file":"Video.js","sources":["src/Video.tsx"],"sourcesContent":["import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';\n\nimport { VideoControls } from '@hh.ru/magritte-ui-video/VideoControls';\nimport { VideoPlayer } from '@hh.ru/magritte-ui-video/VideoPlayer';\nimport { VideoContext, type VideoContextValue } from '@hh.ru/magritte-ui-video/internal/context';\nimport { createVideoStore, type VideoPlaybackOptions } from '@hh.ru/magritte-ui-video/internal/store';\nimport { type VideoHandle, type VideoProps } from '@hh.ru/magritte-ui-video/types';\n\ninterface VideoComponents {\n Player: typeof VideoPlayer;\n Controls: typeof VideoControls;\n}\n\n/**\n * Корень видео: хранит состояние воспроизведения и раздаёт его частям (`Video.Player`, `Video.Controls`).\n * Собственной разметки не создаёт\n */\nexport const Video = forwardRef<VideoHandle, VideoProps>(\n (\n {\n src,\n sources,\n preview,\n duration,\n attachMedia,\n autoPlay = false,\n autoPause = false,\n loop = false,\n defaultMuted,\n defaultVolume,\n interactive = true,\n onPlay,\n onPause,\n onEnded,\n onError,\n children,\n },\n ref\n ) => {\n const callbacksRef = useRef({ attachMedia, onPlay, onPause, onEnded, onError });\n callbacksRef.current = { attachMedia, onPlay, onPause, onEnded, onError };\n const optionsRef = useRef<VideoPlaybackOptions>({ autoPlay, autoPause, loop, interactive });\n optionsRef.current = { autoPlay, autoPause, loop, interactive };\n\n const [store] = useState(() =>\n createVideoStore({\n src,\n sources,\n defaultMuted,\n defaultVolume,\n getCallbacks: () => callbacksRef.current,\n getOptions: () => optionsRef.current,\n })\n );\n\n useEffect(() => {\n store.syncOptions();\n }, [store, autoPlay, autoPause, loop, interactive]);\n\n // стор сравнивает источники по значению — новый массив sources на каждый рендер не перезагружает видео\n useEffect(() => {\n store.setSource({ src, sources });\n }, [store, src, sources]);\n\n useImperativeHandle(\n ref,\n () => ({\n play: store.play,\n pause: store.pause,\n seek: store.seek,\n getVideoElement: store.getVideoElement,\n }),\n [store]\n );\n\n const contextValue = useMemo<VideoContextValue>(\n () => ({\n store,\n preview,\n duration,\n interactive,\n // плееру нужна видимость во вьюпорте: для автовоспроизведения и автопаузы\n observeViewport: autoPlay || autoPause || !interactive,\n }),\n [store, preview, duration, interactive, autoPlay, autoPause]\n );\n\n return <VideoContext.Provider value={contextValue}>{children}</VideoContext.Provider>;\n }\n) as React.ForwardRefExoticComponent<VideoProps & React.RefAttributes<VideoHandle>> & VideoComponents;\n\nVideo.displayName = 'Video';\n\nVideo.Player = VideoPlayer;\nVideo.Controls = VideoControls;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiBO,MAAM,KAAQ,GAAA,UAAA;AAAA,EACjB,CACI;AAAA,IACI,GAAA;AAAA,IACA,OAAA;AAAA,IACA,OAAA;AAAA,IACA,QAAA;AAAA,IACA,WAAA;AAAA,IACA,QAAW,GAAA,KAAA;AAAA,IACX,SAAY,GAAA,KAAA;AAAA,IACZ,IAAO,GAAA,KAAA;AAAA,IACP,YAAA;AAAA,IACA,aAAA;AAAA,IACA,WAAc,GAAA,IAAA;AAAA,IACd,MAAA;AAAA,IACA,OAAA;AAAA,IACA,OAAA;AAAA,IACA,OAAA;AAAA,IACA,QAAA;AAAA,KAEJ,GACC,KAAA;AACD,IAAM,MAAA,YAAA,GAAe,OAAO,EAAE,WAAA,EAAa,QAAQ,OAAS,EAAA,OAAA,EAAS,SAAS,CAAA,CAAA;AAC9E,IAAA,YAAA,CAAa,UAAU,EAAE,WAAA,EAAa,MAAQ,EAAA,OAAA,EAAS,SAAS,OAAQ,EAAA,CAAA;AACxE,IAAA,MAAM,aAAa,MAA6B,CAAA,EAAE,UAAU,SAAW,EAAA,IAAA,EAAM,aAAa,CAAA,CAAA;AAC1F,IAAA,UAAA,CAAW,OAAU,GAAA,EAAE,QAAU,EAAA,SAAA,EAAW,MAAM,WAAY,EAAA,CAAA;AAE9D,IAAM,MAAA,CAAC,KAAK,CAAI,GAAA,QAAA;AAAA,MAAS,MACrB,gBAAiB,CAAA;AAAA,QACb,GAAA;AAAA,QACA,OAAA;AAAA,QACA,YAAA;AAAA,QACA,aAAA;AAAA,QACA,YAAA,EAAc,MAAM,YAAa,CAAA,OAAA;AAAA,QACjC,UAAA,EAAY,MAAM,UAAW,CAAA,OAAA;AAAA,OAChC,CAAA;AAAA,KACL,CAAA;AAEA,IAAA,SAAA,CAAU,MAAM;AACZ,MAAA,KAAA,CAAM,WAAY,EAAA,CAAA;AAAA,OACnB,CAAC,KAAA,EAAO,UAAU,SAAW,EAAA,IAAA,EAAM,WAAW,CAAC,CAAA,CAAA;AAGlD,IAAA,SAAA,CAAU,MAAM;AACZ,MAAA,KAAA,CAAM,SAAU,CAAA,EAAE,GAAK,EAAA,OAAA,EAAS,CAAA,CAAA;AAAA,KACjC,EAAA,CAAC,KAAO,EAAA,GAAA,EAAK,OAAO,CAAC,CAAA,CAAA;AAExB,IAAA,mBAAA;AAAA,MACI,GAAA;AAAA,MACA,OAAO;AAAA,QACH,MAAM,KAAM,CAAA,IAAA;AAAA,QACZ,OAAO,KAAM,CAAA,KAAA;AAAA,QACb,MAAM,KAAM,CAAA,IAAA;AAAA,QACZ,iBAAiB,KAAM,CAAA,eAAA;AAAA,OAC3B,CAAA;AAAA,MACA,CAAC,KAAK,CAAA;AAAA,KACV,CAAA;AAEA,IAAA,MAAM,YAAe,GAAA,OAAA;AAAA,MACjB,OAAO;AAAA,QACH,KAAA;AAAA,QACA,OAAA;AAAA,QACA,QAAA;AAAA,QACA,WAAA;AAAA;AAAA,QAEA,eAAA,EAAiB,QAAY,IAAA,SAAA,IAAa,CAAC,WAAA;AAAA,OAC/C,CAAA;AAAA,MACA,CAAC,KAAO,EAAA,OAAA,EAAS,QAAU,EAAA,WAAA,EAAa,UAAU,SAAS,CAAA;AAAA,KAC/D,CAAA;AAEA,IAAA,2BAAQ,YAAa,CAAA,QAAA,EAAb,EAAsB,KAAA,EAAO,cAAe,QAAS,EAAA,CAAA,CAAA;AAAA,GACjE;AACJ,EAAA;AAEA,KAAA,CAAM,WAAc,GAAA,OAAA,CAAA;AAEpB,KAAA,CAAM,MAAS,GAAA,WAAA,CAAA;AACf,KAAA,CAAM,QAAW,GAAA,aAAA;;;;"}
package/VideoControls.js CHANGED
@@ -15,7 +15,7 @@ import { useVideoContext, VideoPlayerContext, useVideoState } from './internal/c
15
15
  import { findFocusableControl, CONTROL_KEY_ATTRIBUTE } from './internal/controlKey.js';
16
16
  import { useInert } from './internal/useInert.js';
17
17
  import { useVideoHotkeys } from './internal/useVideoHotkeys.js';
18
- import { s as styles } from './video-C-iklAZX.js';
18
+ import { s as styles } from './video-BnfN4Emx.js';
19
19
  import '@hh.ru/magritte-ui-tooltip';
20
20
  import './internal/formatTime.js';
21
21
  import './internal/useSliderPointer.js';
@@ -23,7 +23,7 @@ import '@hh.ru/magritte-common-keyboard';
23
23
 
24
24
  const STATUSES_WITH_CONTROLS = ["loading", "playing", "paused", "ended"];
25
25
  const VideoControls = ({ "data-qa": dataQa = "video-controls" }) => {
26
- const { store } = useVideoContext("Video.Controls");
26
+ const { store, interactive } = useVideoContext("Video.Controls");
27
27
  const insidePlayer = useContext(VideoPlayerContext);
28
28
  const { isMobile } = useBreakpoint();
29
29
  const status = useVideoState(store, (state) => state.status);
@@ -40,7 +40,7 @@ const VideoControls = ({ "data-qa": dataQa = "video-controls" }) => {
40
40
  const restoringFocusRef = useRef(false);
41
41
  useInert(rootRef, !visible);
42
42
  const onKeyDown = useVideoHotkeys(store);
43
- const hasControls = STATUSES_WITH_CONTROLS.includes(status);
43
+ const hasControls = interactive && STATUSES_WITH_CONTROLS.includes(status);
44
44
  useEffect(
45
45
  () => () => {
46
46
  if (holdingRef.current) {
@@ -208,7 +208,7 @@ const VideoControls = ({ "data-qa": dataQa = "video-controls" }) => {
208
208
  const target = fullscreen && store.getRoot() || container;
209
209
  return target ? createPortal(renderResult, target) : renderResult;
210
210
  };
211
- VideoControls.displayName = "Video.Controls";
211
+ VideoControls.displayName = "VideoControls";
212
212
 
213
213
  export { VideoControls };
214
214
  //# sourceMappingURL=VideoControls.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"VideoControls.js","sources":["src/VideoControls.tsx"],"sourcesContent":["import { type FC, type FocusEvent, useContext, useEffect, useLayoutEffect, useRef } from 'react';\nimport { createPortal } from 'react-dom';\nimport classnames from 'classnames';\n\nimport { useTranslation } from '@hh.ru/magritte-common-use-translation';\nimport { useBreakpoint } from '@hh.ru/magritte-ui-breakpoint';\nimport {\n ArrowPathClockwiseOutlinedSize24,\n DashedSquareInOutlinedSize24,\n DashedSquareOutOutlinedSize24,\n PauseFilledSize24,\n PlayFilledSize24,\n SpeakerCrossedOutlinedSize24,\n SpeakerWaveOutlinedSize24,\n} from '@hh.ru/magritte-ui-icon/icon';\nimport { Text } from '@hh.ru/magritte-ui-typography';\nimport { ControlButton } from '@hh.ru/magritte-ui-video/internal/ControlButton';\nimport { Scrubber } from '@hh.ru/magritte-ui-video/internal/Scrubber';\nimport { TimeLabel } from '@hh.ru/magritte-ui-video/internal/TimeLabel';\nimport { VolumeSlider } from '@hh.ru/magritte-ui-video/internal/VolumeSlider';\nimport { useVideoContext, useVideoState, VideoPlayerContext } from '@hh.ru/magritte-ui-video/internal/context';\nimport { CONTROL_KEY_ATTRIBUTE, findFocusableControl } from '@hh.ru/magritte-ui-video/internal/controlKey';\nimport { useInert } from '@hh.ru/magritte-ui-video/internal/useInert';\nimport { useVideoHotkeys } from '@hh.ru/magritte-ui-video/internal/useVideoHotkeys';\nimport { type VideoControlsProps, type VideoStatus } from '@hh.ru/magritte-ui-video/types';\n\nimport styles from './video.less';\n\n/** Контролы есть только после запуска: в default и при ошибке их нет */\nconst STATUSES_WITH_CONTROLS: VideoStatus[] = ['loading', 'playing', 'paused', 'ended'];\n\n/**\n * Видеоконтролы: перемотка, время, play/pause (desktop), скорость, громкость, fullscreen.\n * Внутри `Video.Player` раскладываются поверх видео у нижнего края, снаружи — в потоке.\n * Стор может перенести их в другой контейнер (`setControlsContainer`), в fullscreen — всегда внутрь плеера\n */\nexport const VideoControls: FC<VideoControlsProps> = ({ 'data-qa': dataQa = 'video-controls' }) => {\n const { store } = useVideoContext('Video.Controls');\n const insidePlayer = useContext(VideoPlayerContext);\n const { isMobile } = useBreakpoint();\n\n const status = useVideoState(store, (state) => state.status);\n const visible = useVideoState(store, (state) => state.controlsVisible);\n const live = useVideoState(store, (state) => state.duration === null);\n const rate = useVideoState(store, (state) => state.rate);\n const muted = useVideoState(store, (state) => state.muted);\n const fullscreen = useVideoState(store, (state) => state.fullscreen);\n const container = useVideoState(store, (state) => state.controlsContainer);\n const rootRef = useRef<HTMLDivElement>(null);\n const pointerFocusRef = useRef(false);\n const holdingRef = useRef(false);\n // имя контрола с фокусом (`data-video-control`) — по нему фокус возвращается, если узел под ним подменили\n const focusedControlRef = useRef<string | null>(null);\n const restoringFocusRef = useRef(false);\n useInert(rootRef, !visible);\n // контролы бывают вне плеера (в потоке, во вьювере) — фокус в них тоже «фокус в видео»\n const onKeyDown = useVideoHotkeys(store);\n\n const hasControls = STATUSES_WITH_CONTROLS.includes(status);\n\n // Удержание (фокус с клавиатуры внутри) снимается по blur. Но если контролы пропали вместе с фокусом —\n // ошибка или смена источника (компонент рендерит null) либо размонтирование, — blur не придёт:\n // отпускаем удержание сами, иначе контролы больше никогда не спрячутся\n useEffect(\n () => () => {\n if (holdingRef.current) {\n holdingRef.current = false;\n store.holdControls(false);\n }\n },\n [store, hasControls]\n );\n\n // Кнопка со сменой иконки (звук, play/pause, fullscreen) пересоздаётся: у каждой иконки свой компонент.\n // Вход и выход из fullscreen переносит контролы в портал и обратно — пересоздаются все. Фокус в обоих случаях\n // падает на body без blur: возвращаем его на тот же контрол, чтобы клавиатурная навигация продолжалась с места\n useLayoutEffect(() => {\n const root = rootRef.current;\n if (!root || !visible) {\n focusedControlRef.current = null;\n return;\n }\n const key = focusedControlRef.current;\n const active = document.activeElement;\n if (!key || (active && active !== document.body)) {\n return;\n }\n const control = findFocusableControl(root, key);\n if (!control) {\n focusedControlRef.current = null;\n return;\n }\n // фокус не новый — удержание контролов остаётся как было\n restoringFocusRef.current = true;\n control.focus({ preventScroll: true });\n restoringFocusRef.current = false;\n });\n\n const controlsTrl = useTranslation('Video', 'controls');\n const playTrl = useTranslation('Video', 'play');\n const pauseTrl = useTranslation('Video', 'pause');\n const replayTrl = useTranslation('Video', 'replay');\n const playbackRateTrl = useTranslation('Video', 'playbackRate');\n const muteTrl = useTranslation('Video', 'mute');\n const unmuteTrl = useTranslation('Video', 'unmute');\n const enterFullscreenTrl = useTranslation('Video', 'enterFullscreen');\n const exitFullscreenTrl = useTranslation('Video', 'exitFullscreen');\n\n if (!hasControls) {\n return null;\n }\n\n const withTooltip = !isMobile && visible;\n // live до загрузки метаданных не отличить от обычного видео — ползунок прячем только когда длительность так и не появилась\n const showScrubber = !live || status === 'loading';\n\n // фокус с клавиатуры внутри контролов — взаимодействие: пока он там, контролы не прячутся.\n // Фокус после клика мышью не держит контролы, иначе они не спрячутся никогда\n const onFocus = (event: FocusEvent<HTMLDivElement>) => {\n focusedControlRef.current =\n (event.target as HTMLElement).closest(`[${CONTROL_KEY_ATTRIBUTE}]`)?.getAttribute(CONTROL_KEY_ATTRIBUTE) ??\n null;\n const pointerFocus = pointerFocusRef.current;\n pointerFocusRef.current = false;\n if (\n restoringFocusRef.current ||\n pointerFocus ||\n holdingRef.current ||\n event.currentTarget.contains(event.relatedTarget)\n ) {\n return;\n }\n holdingRef.current = true;\n store.holdControls(true);\n };\n const onBlur = (event: FocusEvent<HTMLDivElement>) => {\n if (!event.currentTarget.contains(event.relatedTarget)) {\n focusedControlRef.current = null;\n }\n if (!holdingRef.current || event.currentTarget.contains(event.relatedTarget)) {\n return;\n }\n holdingRef.current = false;\n store.holdControls(false);\n };\n\n let playPause = { label: playTrl, icon: PlayFilledSize24 };\n if (status === 'ended') {\n playPause = { label: replayTrl, icon: ArrowPathClockwiseOutlinedSize24 };\n } else if (status === 'playing') {\n playPause = { label: pauseTrl, icon: PauseFilledSize24 };\n }\n\n const renderResult = (\n <div\n ref={rootRef}\n role=\"group\"\n aria-label={controlsTrl}\n className={classnames(styles.controls, {\n [styles.controlsMobile]: isMobile,\n [styles.controlsOverlay]: fullscreen || (insidePlayer && !container),\n [styles.controlsHidden]: !visible,\n })}\n // холст игнорирует события из контролов по этому атрибуту\n data-video-controls\n data-qa={dataQa}\n onPointerDownCapture={() => {\n pointerFocusRef.current = true;\n }}\n // фокус от клика приходит до pointerup; если его не было — сбрасываем флаг\n onPointerUpCapture={() => {\n pointerFocusRef.current = false;\n }}\n onPointerMove={(event) => event.pointerType === 'mouse' && store.notifyActivity()}\n onFocus={onFocus}\n onBlur={onBlur}\n onKeyDown={onKeyDown}\n >\n {showScrubber && <Scrubber store={store} />}\n <div className={styles.controlsRow}>\n <div className={styles.controlsGroupStart}>\n {!isMobile && (\n <ControlButton\n label={playPause.label}\n icon={playPause.icon}\n onClick={() => {\n store.toggle();\n store.notifyActivity();\n }}\n withTooltip={withTooltip}\n control=\"play-pause\"\n data-qa=\"video-play-pause\"\n />\n )}\n <div className={styles.controlsTime}>\n <TimeLabel store={store} live={live && status !== 'loading'} />\n </div>\n </div>\n <div className={styles.controlsGroupEnd}>\n <ControlButton\n label={`${playbackRateTrl ?? ''}: ${rate}x`}\n onClick={() => {\n store.cycleRate();\n store.notifyActivity();\n }}\n withTooltip={withTooltip}\n control=\"playback-rate\"\n data-qa=\"video-playback-rate\"\n >\n <Text typography=\"subtitle-2-semibold\" style=\"constant\">\n {`${rate}x`}\n </Text>\n </ControlButton>\n <div className={styles.controlsVolume}>\n <ControlButton\n label={muted ? unmuteTrl : muteTrl}\n icon={muted ? SpeakerCrossedOutlinedSize24 : SpeakerWaveOutlinedSize24}\n onClick={() => {\n store.setMuted(!muted);\n store.notifyActivity();\n }}\n withTooltip={withTooltip}\n bare\n control=\"mute\"\n data-qa=\"video-mute\"\n />\n {!isMobile && <VolumeSlider store={store} />}\n </div>\n <ControlButton\n label={fullscreen ? exitFullscreenTrl : enterFullscreenTrl}\n icon={fullscreen ? DashedSquareInOutlinedSize24 : DashedSquareOutOutlinedSize24}\n onClick={() => {\n store.toggleFullscreen();\n store.notifyActivity();\n }}\n withTooltip={withTooltip}\n control=\"fullscreen\"\n data-qa=\"video-fullscreen\"\n />\n </div>\n </div>\n </div>\n );\n\n // в fullscreen разворачивается плеер — контролы переезжают в него; иначе — в заданный контейнер, если он есть\n const target = (fullscreen && store.getRoot()) || container;\n return target ? createPortal(renderResult, target) : renderResult;\n};\n\nVideoControls.displayName = 'Video.Controls';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"VideoControls.js","sources":["src/VideoControls.tsx"],"sourcesContent":["import { type FC, type FocusEvent, useContext, useEffect, useLayoutEffect, useRef } from 'react';\nimport { createPortal } from 'react-dom';\nimport classnames from 'classnames';\n\nimport { useTranslation } from '@hh.ru/magritte-common-use-translation';\nimport { useBreakpoint } from '@hh.ru/magritte-ui-breakpoint';\nimport {\n ArrowPathClockwiseOutlinedSize24,\n DashedSquareInOutlinedSize24,\n DashedSquareOutOutlinedSize24,\n PauseFilledSize24,\n PlayFilledSize24,\n SpeakerCrossedOutlinedSize24,\n SpeakerWaveOutlinedSize24,\n} from '@hh.ru/magritte-ui-icon/icon';\nimport { Text } from '@hh.ru/magritte-ui-typography';\nimport { ControlButton } from '@hh.ru/magritte-ui-video/internal/ControlButton';\nimport { Scrubber } from '@hh.ru/magritte-ui-video/internal/Scrubber';\nimport { TimeLabel } from '@hh.ru/magritte-ui-video/internal/TimeLabel';\nimport { VolumeSlider } from '@hh.ru/magritte-ui-video/internal/VolumeSlider';\nimport { useVideoContext, useVideoState, VideoPlayerContext } from '@hh.ru/magritte-ui-video/internal/context';\nimport { CONTROL_KEY_ATTRIBUTE, findFocusableControl } from '@hh.ru/magritte-ui-video/internal/controlKey';\nimport { useInert } from '@hh.ru/magritte-ui-video/internal/useInert';\nimport { useVideoHotkeys } from '@hh.ru/magritte-ui-video/internal/useVideoHotkeys';\nimport { type VideoControlsProps, type VideoStatus } from '@hh.ru/magritte-ui-video/types';\n\nimport styles from './video.less';\n\n/** Контролы есть только после запуска: в default и при ошибке их нет */\nconst STATUSES_WITH_CONTROLS: VideoStatus[] = ['loading', 'playing', 'paused', 'ended'];\n\n/**\n * Видеоконтролы: перемотка, время, play/pause (desktop), скорость, громкость, fullscreen.\n * Внутри `Video.Player` раскладываются поверх видео у нижнего края, снаружи — в потоке.\n * Стор может перенести их в другой контейнер (`setControlsContainer`), в fullscreen — всегда внутрь плеера\n */\nexport const VideoControls: FC<VideoControlsProps> = ({ 'data-qa': dataQa = 'video-controls' }) => {\n const { store, interactive } = useVideoContext('Video.Controls');\n const insidePlayer = useContext(VideoPlayerContext);\n const { isMobile } = useBreakpoint();\n\n const status = useVideoState(store, (state) => state.status);\n const visible = useVideoState(store, (state) => state.controlsVisible);\n const live = useVideoState(store, (state) => state.duration === null);\n const rate = useVideoState(store, (state) => state.rate);\n const muted = useVideoState(store, (state) => state.muted);\n const fullscreen = useVideoState(store, (state) => state.fullscreen);\n const container = useVideoState(store, (state) => state.controlsContainer);\n const rootRef = useRef<HTMLDivElement>(null);\n const pointerFocusRef = useRef(false);\n const holdingRef = useRef(false);\n // имя контрола с фокусом (`data-video-control`) — по нему фокус возвращается, если узел под ним подменили\n const focusedControlRef = useRef<string | null>(null);\n const restoringFocusRef = useRef(false);\n useInert(rootRef, !visible);\n // контролы бывают вне плеера (в потоке, во вьювере) — фокус в них тоже «фокус в видео»\n const onKeyDown = useVideoHotkeys(store);\n\n // в неинтерактивном режиме контролов нет\n const hasControls = interactive && STATUSES_WITH_CONTROLS.includes(status);\n\n // Удержание (фокус с клавиатуры внутри) снимается по blur. Но если контролы пропали вместе с фокусом —\n // ошибка или смена источника (компонент рендерит null) либо размонтирование, — blur не придёт:\n // отпускаем удержание сами, иначе контролы больше никогда не спрячутся\n useEffect(\n () => () => {\n if (holdingRef.current) {\n holdingRef.current = false;\n store.holdControls(false);\n }\n },\n [store, hasControls]\n );\n\n // Кнопка со сменой иконки (звук, play/pause, fullscreen) пересоздаётся: у каждой иконки свой компонент.\n // Вход и выход из fullscreen переносит контролы в портал и обратно — пересоздаются все. Фокус в обоих случаях\n // падает на body без blur: возвращаем его на тот же контрол, чтобы клавиатурная навигация продолжалась с места\n useLayoutEffect(() => {\n const root = rootRef.current;\n if (!root || !visible) {\n focusedControlRef.current = null;\n return;\n }\n const key = focusedControlRef.current;\n const active = document.activeElement;\n if (!key || (active && active !== document.body)) {\n return;\n }\n const control = findFocusableControl(root, key);\n if (!control) {\n focusedControlRef.current = null;\n return;\n }\n // фокус не новый — удержание контролов остаётся как было\n restoringFocusRef.current = true;\n control.focus({ preventScroll: true });\n restoringFocusRef.current = false;\n });\n\n const controlsTrl = useTranslation('Video', 'controls');\n const playTrl = useTranslation('Video', 'play');\n const pauseTrl = useTranslation('Video', 'pause');\n const replayTrl = useTranslation('Video', 'replay');\n const playbackRateTrl = useTranslation('Video', 'playbackRate');\n const muteTrl = useTranslation('Video', 'mute');\n const unmuteTrl = useTranslation('Video', 'unmute');\n const enterFullscreenTrl = useTranslation('Video', 'enterFullscreen');\n const exitFullscreenTrl = useTranslation('Video', 'exitFullscreen');\n\n if (!hasControls) {\n return null;\n }\n\n const withTooltip = !isMobile && visible;\n // live до загрузки метаданных не отличить от обычного видео — ползунок прячем только когда длительность так и не появилась\n const showScrubber = !live || status === 'loading';\n\n // фокус с клавиатуры внутри контролов — взаимодействие: пока он там, контролы не прячутся.\n // Фокус после клика мышью не держит контролы, иначе они не спрячутся никогда\n const onFocus = (event: FocusEvent<HTMLDivElement>) => {\n focusedControlRef.current =\n (event.target as HTMLElement).closest(`[${CONTROL_KEY_ATTRIBUTE}]`)?.getAttribute(CONTROL_KEY_ATTRIBUTE) ??\n null;\n const pointerFocus = pointerFocusRef.current;\n pointerFocusRef.current = false;\n if (\n restoringFocusRef.current ||\n pointerFocus ||\n holdingRef.current ||\n event.currentTarget.contains(event.relatedTarget)\n ) {\n return;\n }\n holdingRef.current = true;\n store.holdControls(true);\n };\n const onBlur = (event: FocusEvent<HTMLDivElement>) => {\n if (!event.currentTarget.contains(event.relatedTarget)) {\n focusedControlRef.current = null;\n }\n if (!holdingRef.current || event.currentTarget.contains(event.relatedTarget)) {\n return;\n }\n holdingRef.current = false;\n store.holdControls(false);\n };\n\n let playPause = { label: playTrl, icon: PlayFilledSize24 };\n if (status === 'ended') {\n playPause = { label: replayTrl, icon: ArrowPathClockwiseOutlinedSize24 };\n } else if (status === 'playing') {\n playPause = { label: pauseTrl, icon: PauseFilledSize24 };\n }\n\n const renderResult = (\n <div\n ref={rootRef}\n role=\"group\"\n aria-label={controlsTrl}\n className={classnames(styles.controls, {\n [styles.controlsMobile]: isMobile,\n [styles.controlsOverlay]: fullscreen || (insidePlayer && !container),\n [styles.controlsHidden]: !visible,\n })}\n // холст игнорирует события из контролов по этому атрибуту\n data-video-controls\n data-qa={dataQa}\n onPointerDownCapture={() => {\n pointerFocusRef.current = true;\n }}\n // фокус от клика приходит до pointerup; если его не было — сбрасываем флаг\n onPointerUpCapture={() => {\n pointerFocusRef.current = false;\n }}\n onPointerMove={(event) => event.pointerType === 'mouse' && store.notifyActivity()}\n onFocus={onFocus}\n onBlur={onBlur}\n onKeyDown={onKeyDown}\n >\n {showScrubber && <Scrubber store={store} />}\n <div className={styles.controlsRow}>\n <div className={styles.controlsGroupStart}>\n {!isMobile && (\n <ControlButton\n label={playPause.label}\n icon={playPause.icon}\n onClick={() => {\n store.toggle();\n store.notifyActivity();\n }}\n withTooltip={withTooltip}\n control=\"play-pause\"\n data-qa=\"video-play-pause\"\n />\n )}\n <div className={styles.controlsTime}>\n <TimeLabel store={store} live={live && status !== 'loading'} />\n </div>\n </div>\n <div className={styles.controlsGroupEnd}>\n <ControlButton\n label={`${playbackRateTrl ?? ''}: ${rate}x`}\n onClick={() => {\n store.cycleRate();\n store.notifyActivity();\n }}\n withTooltip={withTooltip}\n control=\"playback-rate\"\n data-qa=\"video-playback-rate\"\n >\n <Text typography=\"subtitle-2-semibold\" style=\"constant\">\n {`${rate}x`}\n </Text>\n </ControlButton>\n <div className={styles.controlsVolume}>\n <ControlButton\n label={muted ? unmuteTrl : muteTrl}\n icon={muted ? SpeakerCrossedOutlinedSize24 : SpeakerWaveOutlinedSize24}\n onClick={() => {\n store.setMuted(!muted);\n store.notifyActivity();\n }}\n withTooltip={withTooltip}\n bare\n control=\"mute\"\n data-qa=\"video-mute\"\n />\n {!isMobile && <VolumeSlider store={store} />}\n </div>\n <ControlButton\n label={fullscreen ? exitFullscreenTrl : enterFullscreenTrl}\n icon={fullscreen ? DashedSquareInOutlinedSize24 : DashedSquareOutOutlinedSize24}\n onClick={() => {\n store.toggleFullscreen();\n store.notifyActivity();\n }}\n withTooltip={withTooltip}\n control=\"fullscreen\"\n data-qa=\"video-fullscreen\"\n />\n </div>\n </div>\n </div>\n );\n\n // в fullscreen разворачивается плеер — контролы переезжают в него; иначе — в заданный контейнер, если он есть\n const target = (fullscreen && store.getRoot()) || container;\n return target ? createPortal(renderResult, target) : renderResult;\n};\n\nVideoControls.displayName = 'VideoControls';\n"],"names":[],"mappings":"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"}
package/VideoPlayer.js CHANGED
@@ -18,7 +18,8 @@ import { BOOST_RATE } from './internal/store.js';
18
18
  import { useCanvasGestures } from './internal/useCanvasGestures.js';
19
19
  import { useInert } from './internal/useInert.js';
20
20
  import { useVideoHotkeys } from './internal/useVideoHotkeys.js';
21
- import { s as styles } from './video-C-iklAZX.js';
21
+ import { useViewportVisibility } from './internal/useViewportVisibility.js';
22
+ import { s as styles } from './video-BnfN4Emx.js';
22
23
  import '@hh.ru/magritte-common-keyboard';
23
24
 
24
25
  const ERROR_PLACEHOLDER_MAX_WIDTH = 343;
@@ -37,7 +38,7 @@ const VideoPlayer = ({
37
38
  "data-qa": dataQa = "video-player",
38
39
  children
39
40
  }) => {
40
- const { store, preview, duration: durationProp } = useVideoContext("Video.Player");
41
+ const { store, preview, duration: durationProp, interactive, observeViewport } = useVideoContext("Video.Player");
41
42
  const status = useVideoState(store, (state) => state.status);
42
43
  const waiting = useVideoState(store, (state) => state.waiting);
43
44
  const metadataDuration = useVideoState(store, (state) => state.duration);
@@ -45,6 +46,7 @@ const VideoPlayer = ({
45
46
  const fullscreen = useVideoState(store, (state) => state.fullscreen);
46
47
  const boosted = useVideoState(store, (state) => state.boosted);
47
48
  const indicatorContainer = useVideoState(store, (state) => state.indicatorContainer);
49
+ const retrying = useVideoState(store, (state) => state.retrying);
48
50
  const host = useContext(VideoPlayerHostContext);
49
51
  const { isMobile } = useBreakpoint();
50
52
  const centerButtonRef = useRef(null);
@@ -72,6 +74,8 @@ const VideoPlayer = ({
72
74
  useEffect(() => store.setHideWhenPaused(isMobile), [store, isMobile]);
73
75
  const { seekIndicator, handlers } = useCanvasGestures({ store, isMobile, isOwnInteractiveTarget, keepFocus });
74
76
  const onKeyDown = useVideoHotkeys(store);
77
+ useViewportVisibility(rootRef, store, observeViewport);
78
+ const showError = interactive && status === "error";
75
79
  let CenterIcon = PlayFilledSize24;
76
80
  let centerLabel = playTrl;
77
81
  if (status === "ended") {
@@ -92,7 +96,7 @@ const VideoPlayer = ({
92
96
  "video",
93
97
  {
94
98
  ref: store.attach,
95
- className: classnames(styles.video, { [styles.videoHidden]: status === "error" }),
99
+ className: classnames(styles.video, { [styles.videoHidden]: showError || retrying }),
96
100
  poster: preview,
97
101
  playsInline: true,
98
102
  preload: "metadata"
@@ -115,11 +119,10 @@ const VideoPlayer = ({
115
119
  },
116
120
  "data-qa": dataQa,
117
121
  "data-status": status,
118
- onKeyDown,
119
- ...handlers,
122
+ ...interactive && { onKeyDown, ...handlers },
120
123
  children: [
121
124
  host ? host.renderVideo(video) : video,
122
- status === "idle" && /* @__PURE__ */ jsxs(Fragment, { children: [
125
+ interactive && status === "idle" && /* @__PURE__ */ jsxs(Fragment, { children: [
123
126
  /* @__PURE__ */ jsx("span", { className: styles.overlayButton, children: /* @__PURE__ */ jsx(
124
127
  PlayFilledSize24,
125
128
  {
@@ -134,7 +137,7 @@ const VideoPlayer = ({
134
137
  ) }),
135
138
  showDuration && duration !== void 0 && /* @__PURE__ */ jsx("span", { className: styles.duration, "data-qa": "video-duration", children: /* @__PURE__ */ jsx(Text, { typography: "paragraph-2-regular", style: "constant", children: formatDuration(duration) }) })
136
139
  ] }),
137
- isMobile && started && !waiting && /* @__PURE__ */ jsx(
140
+ interactive && isMobile && started && !waiting && /* @__PURE__ */ jsx(
138
141
  "span",
139
142
  {
140
143
  ref: centerButtonRef,
@@ -156,7 +159,7 @@ const VideoPlayer = ({
156
159
  seekIndicator && /* @__PURE__ */ jsx(SeekIndicator, { ...seekIndicator }),
157
160
  boosted && renderBoostIndicator(),
158
161
  isLoading && /* @__PURE__ */ jsx("div", { className: styles.loader, children: /* @__PURE__ */ jsx(Loader, { size: 32, color: "constant", "aria-label": loadingTrl }) }),
159
- status === "error" && /* @__PURE__ */ jsx("div", { className: styles.error, "data-qa": "video-error", children: /* @__PURE__ */ jsx(
162
+ showError && /* @__PURE__ */ jsx("div", { className: styles.error, "data-qa": "video-error", children: /* @__PURE__ */ jsx(
160
163
  Placeholder,
161
164
  {
162
165
  title: errorTitleTrl,
@@ -191,7 +194,7 @@ const VideoPlayer = ({
191
194
  }
192
195
  );
193
196
  };
194
- VideoPlayer.displayName = "Video.Player";
197
+ VideoPlayer.displayName = "VideoPlayer";
195
198
 
196
199
  export { VideoPlayer };
197
200
  //# sourceMappingURL=VideoPlayer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"VideoPlayer.js","sources":["src/VideoPlayer.tsx"],"sourcesContent":["import { type FC, useCallback, useContext, useEffect, useRef } from 'react';\nimport { createPortal } from 'react-dom';\nimport classnames from 'classnames';\n\nimport { useTranslation } from '@hh.ru/magritte-common-use-translation';\nimport { useBreakpoint } from '@hh.ru/magritte-ui-breakpoint';\nimport { Button } from '@hh.ru/magritte-ui-button';\nimport { ButtonStack } from '@hh.ru/magritte-ui-button-stack';\nimport {\n ArrowPathClockwiseOutlinedSize24,\n ForwardFilledSize16,\n PauseFilledSize24,\n PlayFilledSize24,\n} from '@hh.ru/magritte-ui-icon/icon';\nimport { Loader } from '@hh.ru/magritte-ui-loader';\nimport { Placeholder } from '@hh.ru/magritte-ui-placeholder';\nimport { Text } from '@hh.ru/magritte-ui-typography';\nimport { SeekIndicator } from '@hh.ru/magritte-ui-video/internal/SeekIndicator';\nimport {\n useVideoContext,\n useVideoState,\n VideoPlayerContext,\n VideoPlayerHostContext,\n} from '@hh.ru/magritte-ui-video/internal/context';\nimport { formatDuration } from '@hh.ru/magritte-ui-video/internal/formatTime';\nimport { BOOST_RATE } from '@hh.ru/magritte-ui-video/internal/store';\nimport { useCanvasGestures } from '@hh.ru/magritte-ui-video/internal/useCanvasGestures';\nimport { useInert } from '@hh.ru/magritte-ui-video/internal/useInert';\nimport { useVideoHotkeys } from '@hh.ru/magritte-ui-video/internal/useVideoHotkeys';\nimport { type VideoPlayerProps } from '@hh.ru/magritte-ui-video/types';\n\nimport styles from './video.less';\n\nconst ERROR_PLACEHOLDER_MAX_WIDTH = 343;\n\n/** Круглая кнопка поверх видео: play в default и центральная play/pause на mobile */\nconst OVERLAY_ICON_PROPS = {\n initialColor: 'constant',\n padding: 16,\n borderRadius: 'halfHeight',\n backgroundStyle: 'overlay',\n} as const;\n\n/** Клики и тапы по контролам и кнопкам — не жесты холста */\nconst isOwnInteractiveTarget = (target: EventTarget): boolean =>\n target instanceof Element && !!target.closest('[data-video-controls], button, [role=\"slider\"]');\n\n/** Холст видео: `<video>` и всё, что поверх него, — кнопка воспроизведения, длительность, загрузка, ошибка */\nexport const VideoPlayer: FC<VideoPlayerProps> = ({\n aspectRatio,\n borderRadius = 0,\n showDuration = true,\n 'aria-label': ariaLabel,\n 'data-qa': dataQa = 'video-player',\n children,\n}) => {\n const { store, preview, duration: durationProp } = useVideoContext('Video.Player');\n const status = useVideoState(store, (state) => state.status);\n const waiting = useVideoState(store, (state) => state.waiting);\n const metadataDuration = useVideoState(store, (state) => state.duration);\n const controlsVisible = useVideoState(store, (state) => state.controlsVisible);\n const fullscreen = useVideoState(store, (state) => state.fullscreen);\n const boosted = useVideoState(store, (state) => state.boosted);\n const indicatorContainer = useVideoState(store, (state) => state.indicatorContainer);\n const host = useContext(VideoPlayerHostContext);\n const { isMobile } = useBreakpoint();\n const centerButtonRef = useRef<HTMLSpanElement>(null);\n useInert(centerButtonRef, !controlsVisible);\n const rootRef = useRef<HTMLDivElement | null>(null);\n const setRoot = useCallback(\n (element: HTMLDivElement | null) => {\n rootRef.current = element;\n store.attachRoot(element);\n },\n [store]\n );\n\n const componentDescriptionTrl = useTranslation('Video', 'componentDescription');\n const playTrl = useTranslation('Video', 'play');\n const loadingTrl = useTranslation('Video', 'loading');\n const errorTitleTrl = useTranslation('Video', 'errorTitle');\n const errorDescriptionTrl = useTranslation('Video', 'errorDescription');\n const retryTrl = useTranslation('Video', 'retry');\n const pauseTrl = useTranslation('Video', 'pause');\n const replayTrl = useTranslation('Video', 'replay');\n\n // кнопка, на которой фокус, сейчас исчезнет — оставляем фокус в плеере, а не на body\n const keepFocus = () => rootRef.current?.focus({ preventScroll: true });\n\n const duration = metadataDuration ?? durationProp;\n const isLoading = status === 'loading' || (status === 'playing' && waiting);\n const started = status === 'playing' || status === 'paused' || status === 'ended';\n\n // mobile: на паузе по центру видео кнопка воспроизведения — контролы можно прятать и на паузе; на desktop\n // поверх видео на паузе ничего нет, контролы остаются\n useEffect(() => store.setHideWhenPaused(isMobile), [store, isMobile]);\n\n const { seekIndicator, handlers } = useCanvasGestures({ store, isMobile, isOwnInteractiveTarget, keepFocus });\n const onKeyDown = useVideoHotkeys(store);\n\n let CenterIcon = PlayFilledSize24;\n let centerLabel = playTrl;\n if (status === 'ended') {\n CenterIcon = ArrowPathClockwiseOutlinedSize24;\n centerLabel = replayTrl;\n } else if (status === 'playing') {\n CenterIcon = PauseFilledSize24;\n centerLabel = pauseTrl;\n }\n\n // плашка ускорения — вверху плеера или в заданном контейнере (под шапкой вьювера); в fullscreen — всегда в плеере\n const renderBoostIndicator = () => {\n const indicator = (\n <span className={styles.boostIndicator} aria-hidden data-qa=\"video-boost\">\n <Text typography=\"label-3-regular\" style=\"constant\">\n {`${BOOST_RATE}x`}\n </Text>\n <ForwardFilledSize16 initialColor=\"constant\" />\n </span>\n );\n return indicatorContainer && !fullscreen ? createPortal(indicator, indicatorContainer) : indicator;\n };\n\n const video = (\n <video\n ref={store.attach}\n className={classnames(styles.video, { [styles.videoHidden]: status === 'error' })}\n poster={preview}\n playsInline\n preload=\"metadata\"\n />\n );\n\n return (\n <div\n ref={setRoot}\n role=\"group\"\n aria-label={ariaLabel ?? componentDescriptionTrl}\n tabIndex={-1}\n className={classnames(styles.player, {\n [styles.playerFit]: aspectRatio === undefined,\n [styles.playerEmbedded]: !!host && !fullscreen,\n })}\n // в fullscreen плеер занимает весь экран — скругление сбрасываем\n style={{\n aspectRatio,\n borderRadius: fullscreen ? 0 : borderRadius,\n }}\n data-qa={dataQa}\n data-status={status}\n onKeyDown={onKeyDown}\n {...handlers}\n >\n {host ? host.renderVideo(video) : video}\n\n {status === 'idle' && (\n <>\n {/* обёртка — позиционирование и размытие подложки, кнопку даёт сама иконка */}\n <span className={styles.overlayButton}>\n <PlayFilledSize24\n {...OVERLAY_ICON_PROPS}\n aria-label={playTrl}\n onClick={() => {\n keepFocus();\n store.play();\n }}\n data-qa=\"video-play\"\n />\n </span>\n {showDuration && duration !== undefined && (\n <span className={styles.duration} data-qa=\"video-duration\">\n <Text typography=\"paragraph-2-regular\" style=\"constant\">\n {formatDuration(duration)}\n </Text>\n </span>\n )}\n </>\n )}\n\n {isMobile && started && !waiting && (\n <span\n ref={centerButtonRef}\n className={classnames(styles.overlayButton, { [styles.overlayButtonHidden]: !controlsVisible })}\n >\n <CenterIcon\n {...OVERLAY_ICON_PROPS}\n aria-label={centerLabel}\n onClick={() => {\n store.toggle();\n store.notifyActivity();\n }}\n data-qa=\"video-center-button\"\n />\n </span>\n )}\n\n {seekIndicator && <SeekIndicator {...seekIndicator} />}\n\n {boosted && renderBoostIndicator()}\n\n {isLoading && (\n <div className={styles.loader}>\n <Loader size={32} color=\"constant\" aria-label={loadingTrl} />\n </div>\n )}\n\n {status === 'error' && (\n <div className={styles.error} data-qa=\"video-error\">\n <Placeholder\n title={errorTitleTrl}\n description={errorDescriptionTrl}\n titleProps={{\n Element: 'div',\n size: 'medium',\n alignment: 'center',\n style: 'constant',\n descriptionStyle: 'constant-secondary',\n notRegionTitle: true,\n }}\n maxWidth={ERROR_PLACEHOLDER_MAX_WIDTH}\n buttons={\n <ButtonStack layout=\"vertical\" stretched>\n <Button\n mode=\"secondary\"\n style=\"constant\"\n size=\"medium\"\n onClick={() => {\n keepFocus();\n store.retry();\n }}\n data-qa=\"video-retry\"\n >\n {retryTrl}\n </Button>\n </ButtonStack>\n }\n />\n </div>\n )}\n\n <VideoPlayerContext.Provider value>{children}</VideoPlayerContext.Provider>\n </div>\n );\n};\n\nVideoPlayer.displayName = 'Video.Player';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"VideoPlayer.js","sources":["src/VideoPlayer.tsx"],"sourcesContent":["import { type FC, useCallback, useContext, useEffect, useRef } from 'react';\nimport { createPortal } from 'react-dom';\nimport classnames from 'classnames';\n\nimport { useTranslation } from '@hh.ru/magritte-common-use-translation';\nimport { useBreakpoint } from '@hh.ru/magritte-ui-breakpoint';\nimport { Button } from '@hh.ru/magritte-ui-button';\nimport { ButtonStack } from '@hh.ru/magritte-ui-button-stack';\nimport {\n ArrowPathClockwiseOutlinedSize24,\n ForwardFilledSize16,\n PauseFilledSize24,\n PlayFilledSize24,\n} from '@hh.ru/magritte-ui-icon/icon';\nimport { Loader } from '@hh.ru/magritte-ui-loader';\nimport { Placeholder } from '@hh.ru/magritte-ui-placeholder';\nimport { Text } from '@hh.ru/magritte-ui-typography';\nimport { SeekIndicator } from '@hh.ru/magritte-ui-video/internal/SeekIndicator';\nimport {\n useVideoContext,\n useVideoState,\n VideoPlayerContext,\n VideoPlayerHostContext,\n} from '@hh.ru/magritte-ui-video/internal/context';\nimport { formatDuration } from '@hh.ru/magritte-ui-video/internal/formatTime';\nimport { BOOST_RATE } from '@hh.ru/magritte-ui-video/internal/store';\nimport { useCanvasGestures } from '@hh.ru/magritte-ui-video/internal/useCanvasGestures';\nimport { useInert } from '@hh.ru/magritte-ui-video/internal/useInert';\nimport { useVideoHotkeys } from '@hh.ru/magritte-ui-video/internal/useVideoHotkeys';\nimport { useViewportVisibility } from '@hh.ru/magritte-ui-video/internal/useViewportVisibility';\nimport { type VideoPlayerProps } from '@hh.ru/magritte-ui-video/types';\n\nimport styles from './video.less';\n\nconst ERROR_PLACEHOLDER_MAX_WIDTH = 343;\n\n/** Круглая кнопка поверх видео: play в default и центральная play/pause на mobile */\nconst OVERLAY_ICON_PROPS = {\n initialColor: 'constant',\n padding: 16,\n borderRadius: 'halfHeight',\n backgroundStyle: 'overlay',\n} as const;\n\n/** Клики и тапы по контролам и кнопкам — не жесты холста */\nconst isOwnInteractiveTarget = (target: EventTarget): boolean =>\n target instanceof Element && !!target.closest('[data-video-controls], button, [role=\"slider\"]');\n\n/** Холст видео: `<video>` и всё, что поверх него, — кнопка воспроизведения, длительность, загрузка, ошибка */\nexport const VideoPlayer: FC<VideoPlayerProps> = ({\n aspectRatio,\n borderRadius = 0,\n showDuration = true,\n 'aria-label': ariaLabel,\n 'data-qa': dataQa = 'video-player',\n children,\n}) => {\n const { store, preview, duration: durationProp, interactive, observeViewport } = useVideoContext('Video.Player');\n const status = useVideoState(store, (state) => state.status);\n const waiting = useVideoState(store, (state) => state.waiting);\n const metadataDuration = useVideoState(store, (state) => state.duration);\n const controlsVisible = useVideoState(store, (state) => state.controlsVisible);\n const fullscreen = useVideoState(store, (state) => state.fullscreen);\n const boosted = useVideoState(store, (state) => state.boosted);\n const indicatorContainer = useVideoState(store, (state) => state.indicatorContainer);\n // повтор после ошибки: до начала воспроизведения — лоадер на тёмном фоне, без превью\n const retrying = useVideoState(store, (state) => state.retrying);\n const host = useContext(VideoPlayerHostContext);\n const { isMobile } = useBreakpoint();\n const centerButtonRef = useRef<HTMLSpanElement>(null);\n useInert(centerButtonRef, !controlsVisible);\n const rootRef = useRef<HTMLDivElement | null>(null);\n const setRoot = useCallback(\n (element: HTMLDivElement | null) => {\n rootRef.current = element;\n store.attachRoot(element);\n },\n [store]\n );\n\n const componentDescriptionTrl = useTranslation('Video', 'componentDescription');\n const playTrl = useTranslation('Video', 'play');\n const loadingTrl = useTranslation('Video', 'loading');\n const errorTitleTrl = useTranslation('Video', 'errorTitle');\n const errorDescriptionTrl = useTranslation('Video', 'errorDescription');\n const retryTrl = useTranslation('Video', 'retry');\n const pauseTrl = useTranslation('Video', 'pause');\n const replayTrl = useTranslation('Video', 'replay');\n\n // кнопка, на которой фокус, сейчас исчезнет — оставляем фокус в плеере, а не на body\n const keepFocus = () => rootRef.current?.focus({ preventScroll: true });\n\n const duration = metadataDuration ?? durationProp;\n const isLoading = status === 'loading' || (status === 'playing' && waiting);\n const started = status === 'playing' || status === 'paused' || status === 'ended';\n\n // mobile: на паузе по центру видео кнопка воспроизведения — контролы можно прятать и на паузе; на desktop\n // поверх видео на паузе ничего нет, контролы остаются\n useEffect(() => store.setHideWhenPaused(isMobile), [store, isMobile]);\n\n const { seekIndicator, handlers } = useCanvasGestures({ store, isMobile, isOwnInteractiveTarget, keepFocus });\n const onKeyDown = useVideoHotkeys(store);\n useViewportVisibility(rootRef, store, observeViewport);\n // неинтерактивное видео: без кнопок, жестов, горячих клавиш и экрана ошибки — только видео и загрузка\n const showError = interactive && status === 'error';\n\n let CenterIcon = PlayFilledSize24;\n let centerLabel = playTrl;\n if (status === 'ended') {\n CenterIcon = ArrowPathClockwiseOutlinedSize24;\n centerLabel = replayTrl;\n } else if (status === 'playing') {\n CenterIcon = PauseFilledSize24;\n centerLabel = pauseTrl;\n }\n\n // плашка ускорения — вверху плеера или в заданном контейнере (под шапкой вьювера); в fullscreen — всегда в плеере\n const renderBoostIndicator = () => {\n const indicator = (\n <span className={styles.boostIndicator} aria-hidden data-qa=\"video-boost\">\n <Text typography=\"label-3-regular\" style=\"constant\">\n {`${BOOST_RATE}x`}\n </Text>\n <ForwardFilledSize16 initialColor=\"constant\" />\n </span>\n );\n return indicatorContainer && !fullscreen ? createPortal(indicator, indicatorContainer) : indicator;\n };\n\n const video = (\n <video\n ref={store.attach}\n className={classnames(styles.video, { [styles.videoHidden]: showError || retrying })}\n poster={preview}\n playsInline\n preload=\"metadata\"\n />\n );\n\n return (\n <div\n ref={setRoot}\n role=\"group\"\n aria-label={ariaLabel ?? componentDescriptionTrl}\n tabIndex={-1}\n className={classnames(styles.player, {\n [styles.playerFit]: aspectRatio === undefined,\n [styles.playerEmbedded]: !!host && !fullscreen,\n })}\n // в fullscreen плеер занимает весь экран — скругление сбрасываем\n style={{\n aspectRatio,\n borderRadius: fullscreen ? 0 : borderRadius,\n }}\n data-qa={dataQa}\n data-status={status}\n {...(interactive && { onKeyDown, ...handlers })}\n >\n {host ? host.renderVideo(video) : video}\n\n {interactive && status === 'idle' && (\n <>\n {/* обёртка — позиционирование и размытие подложки, кнопку даёт сама иконка */}\n <span className={styles.overlayButton}>\n <PlayFilledSize24\n {...OVERLAY_ICON_PROPS}\n aria-label={playTrl}\n onClick={() => {\n keepFocus();\n store.play();\n }}\n data-qa=\"video-play\"\n />\n </span>\n {showDuration && duration !== undefined && (\n <span className={styles.duration} data-qa=\"video-duration\">\n <Text typography=\"paragraph-2-regular\" style=\"constant\">\n {formatDuration(duration)}\n </Text>\n </span>\n )}\n </>\n )}\n\n {interactive && isMobile && started && !waiting && (\n <span\n ref={centerButtonRef}\n className={classnames(styles.overlayButton, { [styles.overlayButtonHidden]: !controlsVisible })}\n >\n <CenterIcon\n {...OVERLAY_ICON_PROPS}\n aria-label={centerLabel}\n onClick={() => {\n store.toggle();\n store.notifyActivity();\n }}\n data-qa=\"video-center-button\"\n />\n </span>\n )}\n\n {seekIndicator && <SeekIndicator {...seekIndicator} />}\n\n {boosted && renderBoostIndicator()}\n\n {isLoading && (\n <div className={styles.loader}>\n <Loader size={32} color=\"constant\" aria-label={loadingTrl} />\n </div>\n )}\n\n {showError && (\n <div className={styles.error} data-qa=\"video-error\">\n <Placeholder\n title={errorTitleTrl}\n description={errorDescriptionTrl}\n titleProps={{\n Element: 'div',\n size: 'medium',\n alignment: 'center',\n style: 'constant',\n descriptionStyle: 'constant-secondary',\n notRegionTitle: true,\n }}\n maxWidth={ERROR_PLACEHOLDER_MAX_WIDTH}\n buttons={\n <ButtonStack layout=\"vertical\" stretched>\n <Button\n mode=\"secondary\"\n style=\"constant\"\n size=\"medium\"\n onClick={() => {\n keepFocus();\n store.retry();\n }}\n data-qa=\"video-retry\"\n >\n {retryTrl}\n </Button>\n </ButtonStack>\n }\n />\n </div>\n )}\n\n <VideoPlayerContext.Provider value>{children}</VideoPlayerContext.Provider>\n </div>\n );\n};\n\nVideoPlayer.displayName = 'VideoPlayer';\n"],"names":[],"mappings":"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"}