diff --git a/package.json b/package.json index a0d9f09..d320166 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "next": "14.2.5", "react": "^18", "react-dom": "^18", + "react-hls-player": "^3.0.7", "tailwind-scrollbar-hide": "^1.1.7", "tailwindcss-animate": "^1.0.7", "ts-pattern": "^5.2.0", diff --git a/src/app/globals.css b/src/app/globals.css index 4068e95..c7e947b 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,3 +1,5 @@ +@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard-dynamic-subset.min.css'); + @tailwind base; @tailwind components; @tailwind utilities; @@ -91,11 +93,19 @@ } html, body { + @apply font-sans; touch-action: none; } + body.video-active * { + pointer-events: none; + } + + body.video-active .video-close { + pointer-events: auto; + } } -@layer typography { +@layer base { /* Headings */ .h1 { @apply text-3xl font-bold leading-[1.6rem] tracking-[0.02rem]; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 0ffad46..e3eb500 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,11 +1,8 @@ import '@/app/globals.css'; import type { Metadata } from 'next'; -import { Inter } from 'next/font/google'; import Providers from './_providers'; -const inter = Inter({ subsets: ['latin'] }); - export const metadata: Metadata = { title: 'WeSki', description: 'We Ski', @@ -14,7 +11,7 @@ export const metadata: Metadata = { export default function RootLayout({ children }: { children: React.ReactNode }) { return ( - + {children} diff --git a/src/app/mobile/congestion/page.tsx b/src/app/mobile/congestion/page.tsx deleted file mode 100644 index 3468067..0000000 --- a/src/app/mobile/congestion/page.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@/pages/congestion/ui/congestion-page'; diff --git a/src/app/mobile/slop-status/page.tsx b/src/app/mobile/slop-status/page.tsx new file mode 100644 index 0000000..834c97b --- /dev/null +++ b/src/app/mobile/slop-status/page.tsx @@ -0,0 +1 @@ +export { default } from '@/pages/slop-status/ui/slop-status-page'; diff --git a/src/app/mobile/weather/layout.tsx b/src/app/mobile/weather/layout.tsx deleted file mode 100644 index b50fc0f..0000000 --- a/src/app/mobile/weather/layout.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { NavBar, StatusBar } from '@/widgets/header/ui'; -import { cn } from '@/shared/lib'; - -export default function Layout({ children }: { children: React.ReactNode }) { - return ( -
-
- -
WeSki
- {children} - -
-
- ); -} diff --git a/src/entities/slop/model/jisan.tsx b/src/entities/slop/model/jisan.tsx index 8a12518..9d9ee58 100644 --- a/src/entities/slop/model/jisan.tsx +++ b/src/entities/slop/model/jisan.tsx @@ -19,6 +19,9 @@ export const JISAN: ResortInfo = { Element: Lemon1LiftPath, webcam: null, isOpen: false, + isDayOpen: false, + isNightOpen: false, + isLateNightOpen: false, }, { id: 'lemon1-1-lift', @@ -27,6 +30,9 @@ export const JISAN: ResortInfo = { Element: Lemon1Sub1LiftPath, webcam: null, isOpen: false, + isDayOpen: false, + isNightOpen: false, + isLateNightOpen: false, }, { id: 'orange2-lift', @@ -40,8 +46,12 @@ export const JISAN: ResortInfo = { top: 'top-[69%]', left: 'left-[29%]', }, + src: 'http://konjiam.live.cdn.cloudn.co.kr/konjiam/cam01.stream/playlist.m3u8', }, isOpen: true, + isDayOpen: false, + isNightOpen: false, + isLateNightOpen: false, }, { id: 'orange3-lift', @@ -55,8 +65,12 @@ export const JISAN: ResortInfo = { top: 'top-[64%]', left: 'left-[38%]', }, + src: 'http://konjiam.live.cdn.cloudn.co.kr/konjiam/cam01.stream/playlist.m3u8', }, isOpen: true, + isDayOpen: false, + isNightOpen: false, + isLateNightOpen: false, }, { id: 'new-orange-lift', @@ -65,6 +79,9 @@ export const JISAN: ResortInfo = { Element: NewOrangeLiftPath, webcam: null, isOpen: false, + isDayOpen: false, + isNightOpen: false, + isLateNightOpen: false, }, { id: 'blue-lift', @@ -80,6 +97,9 @@ export const JISAN: ResortInfo = { }, }, isOpen: true, + isDayOpen: false, + isNightOpen: false, + isLateNightOpen: false, }, { id: 'silver6-lift', @@ -88,6 +108,9 @@ export const JISAN: ResortInfo = { Element: Silver6LiftPath, webcam: null, isOpen: false, + isDayOpen: false, + isNightOpen: false, + isLateNightOpen: false, }, { id: 'silver7-lift', @@ -96,6 +119,9 @@ export const JISAN: ResortInfo = { Element: Silver7LiftPath, webcam: null, isOpen: false, + isDayOpen: false, + isNightOpen: false, + isLateNightOpen: false, }, ], }; diff --git a/src/entities/slop/model/model.d.ts b/src/entities/slop/model/model.d.ts index bdf2946..dfc4639 100644 --- a/src/entities/slop/model/model.d.ts +++ b/src/entities/slop/model/model.d.ts @@ -14,6 +14,10 @@ export type ResortInfo = { name: string; Element: React.FC; isOpen: boolean; + isDayOpen: boolean; + isNightOpen: boolean; + isLateNightOpen: boolean; + webcam: { id: string; name: string; diff --git a/src/entities/slop/ui/level-chip.tsx b/src/entities/slop/ui/level-chip.tsx index 271509e..d6511ed 100644 --- a/src/entities/slop/ui/level-chip.tsx +++ b/src/entities/slop/ui/level-chip.tsx @@ -30,18 +30,20 @@ const LEVEL: Record = { }; interface LevelChipProps { + className?: string; level: Level; } -const LevelChip = ({ level }: LevelChipProps) => { +const LevelChip = ({ level, className }: LevelChipProps) => { return (
-

{LEVEL[level].text}

+

{LEVEL[level].text}

); }; diff --git a/src/widgets/webcam/hooks/useMapPinch.ts b/src/features/slop/hooks/useMapPinch.ts similarity index 89% rename from src/widgets/webcam/hooks/useMapPinch.ts rename to src/features/slop/hooks/useMapPinch.ts index 1200fc4..91f8cbc 100644 --- a/src/widgets/webcam/hooks/useMapPinch.ts +++ b/src/features/slop/hooks/useMapPinch.ts @@ -32,10 +32,8 @@ const useMapPinch = (containerRef: RefObject) => { }, onDrag: ({ pinching, cancel, offset: [x, y] }) => { if (pinching) return cancel(); - api.start({ x, y }); - }, - onDragEnd: () => { - const [boundedX, boundedY] = getBoundedPositions( + + const [{ min: minX, max: maxX }, { min: minY, max: maxY }] = getBoundedPositions( { x: style.x.get(), y: style.y.get(), @@ -46,6 +44,10 @@ const useMapPinch = (containerRef: RefObject) => { height: containerRef.current!.getBoundingClientRect().height, } ); + + const boundedX = Math.min(Math.max(x, minX), maxX); + const boundedY = Math.min(Math.max(y, minY), maxY); + api.start({ x: boundedX, y: boundedY }); }, }, diff --git a/src/features/slop/ui/slop-camera.tsx b/src/features/slop/ui/slop-camera.tsx new file mode 100644 index 0000000..e05fc48 --- /dev/null +++ b/src/features/slop/ui/slop-camera.tsx @@ -0,0 +1,46 @@ +import React, { useState } from 'react'; +import { createPortal } from 'react-dom'; +import { cn } from '@/shared/lib'; +import CameraButton from '@/shared/ui/cam-button'; +import { Tooltip } from '@/shared/ui/tooltip'; +import SlopVideo from './slop-video'; + +interface SlopWebcamProps { + id: string; + name: string; + position: { + top: string; + left: string; + }; + videoSrc?: string; + isOpen: boolean; + renderTarget: React.RefObject; +} + +const SlopCamera = ({ name, position, isOpen, videoSrc, renderTarget }: SlopWebcamProps) => { + const [isVideoOpen, setIsVideoOpen] = useState(false); + + const toggleVideo = () => { + setIsVideoOpen((pre) => !pre); + }; + + return ( + <> +
+
+ } isOpen={isOpen}> +

+ {name} +

+
+
+
+ {renderTarget?.current && + isVideoOpen && + videoSrc && + createPortal(, renderTarget.current)} + + ); +}; + +export default SlopCamera; diff --git a/src/features/slop/ui/slop-map.tsx b/src/features/slop/ui/slop-map.tsx new file mode 100644 index 0000000..c6bfb11 --- /dev/null +++ b/src/features/slop/ui/slop-map.tsx @@ -0,0 +1,54 @@ +import { animated } from '@react-spring/web'; +import type { StaticImageData } from 'next/image'; +import Image from 'next/image'; +import type { ComponentType } from 'react'; +import React from 'react'; +import type { Level } from '@/entities/slop/model/model'; +import { cn } from '@/shared/lib'; +import useMapPinch from '../hooks/useMapPinch'; + +interface SlopMapProps { + containerRef: React.RefObject; + children?: React.ReactNode; + mapSrc: StaticImageData; + + slops: { + id: string; + level: Level; + Element: ComponentType<{ + color?: string; + }>; + }[]; + selectedSlop: string | null; +} + +const SlopMap = ({ containerRef, children, mapSrc, slops, selectedSlop }: SlopMapProps) => { + const { ref, style } = useMapPinch(containerRef); + + return ( + + 이미지 + + {slops.map((slop) => ( +
+ +
+ ))} + + {children} +
+ ); +}; + +export default SlopMap; diff --git a/src/features/slop/ui/slop-status-list.tsx b/src/features/slop/ui/slop-status-list.tsx new file mode 100644 index 0000000..f88af8d --- /dev/null +++ b/src/features/slop/ui/slop-status-list.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import type { Level } from '@/entities/slop/model/model'; +import LevelChip from '@/entities/slop/ui/level-chip'; +import CheckIcon from '@/shared/icons/check'; +import CloseIcon from '@/shared/icons/close'; +import { cn } from '@/shared/lib'; + +interface SlopStatusListProps { + list: { + id: string; + name: string; + level: Level; + isDayOpen: boolean; + isNightOpen: boolean; + isLateNightOpen: boolean; + }[]; + selectedSlop: string | null; + setSelectedSlop: React.Dispatch>; +} + +const SlopStatusList = ({ list, setSelectedSlop, selectedSlop }: SlopStatusListProps) => { + const handleSlopClick = ({ id }: { id: string }) => { + setSelectedSlop(id); + }; + return ( + + + {/* 슬로프명 */} + {/* 난이도 */} + {/* 주간 */} + {/* 야간 */} + {/* 심야 */} + + + + + + + + + + + + {list.map((item, index) => ( + handleSlopClick({ id: item.id })} + > + + + + + + + ))} + +
슬로프명난이도주간야간심야
+ {item.name} + + + + + + + + +
+ ); +}; + +export default SlopStatusList; + +const StatusIcon = ({ isOpen, className }: { isOpen: boolean; className?: string }) => + isOpen ? ( + + ) : ( + + ); diff --git a/src/features/slop/ui/slop-video.tsx b/src/features/slop/ui/slop-video.tsx new file mode 100644 index 0000000..da5e96d --- /dev/null +++ b/src/features/slop/ui/slop-video.tsx @@ -0,0 +1,44 @@ +import React, { useEffect } from 'react'; +import ReactHlsPlayer from 'react-hls-player'; +import { cn } from '@/shared/lib'; +import CloseButton from '@/shared/ui/close-button'; + +interface SlopVideoProps { + src: string; + closeVideo: () => void; +} + +const SlopVideo = ({ src, closeVideo }: SlopVideoProps) => { + const playerRef = React.useRef(null); + + useEffect(() => { + const player = playerRef.current; + + function fireOnVideoStart() { + document.body.classList.add('video-active'); + player?.focus(); + } + + player?.addEventListener('play', fireOnVideoStart); + + return () => { + player?.removeEventListener('play', fireOnVideoStart); + document.body.classList.remove('video-active'); + }; + }, [playerRef]); + + return ( + <> + + + + ); +}; + +export default SlopVideo; diff --git a/src/pages/congestion/ui/congestion-page.tsx b/src/pages/congestion/ui/congestion-page.tsx deleted file mode 100644 index f5b0bb8..0000000 --- a/src/pages/congestion/ui/congestion-page.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import Image from 'next/image'; - -import { cn } from '@/shared/lib'; - -const CongestionPage = () => { - return ( -
- {Array.from({ length: 4 }).map((_, index) => ( - {`${index}`} - ))} -
- ); -}; - -export default CongestionPage; diff --git a/src/pages/slop-status/ui/slop-status-page.tsx b/src/pages/slop-status/ui/slop-status-page.tsx new file mode 100644 index 0000000..7c91d61 --- /dev/null +++ b/src/pages/slop-status/ui/slop-status-page.tsx @@ -0,0 +1,35 @@ +'use client'; + +import { useRef, useState } from 'react'; +import SlopStatusHeader from '@/widgets/header/ui/slop-status-header'; +import SlopMap from '@/features/slop/ui/slop-map'; +import SlopStatusList from '@/features/slop/ui/slop-status-list'; +import { JISAN } from '@/entities/slop/model'; +import { cn } from '@/shared/lib'; + +const SlopStatusPage = () => { + const DUMMY = JISAN; + const ref = useRef(null); + + const [selectedSlop, setSelectedSlop] = useState(null); + return ( +
+ +
+ +
+ +
+ ); +}; + +export default SlopStatusPage; diff --git a/src/pages/webcam/ui/webcam-map-page.tsx b/src/pages/webcam/ui/webcam-map-page.tsx deleted file mode 100644 index 5e58b04..0000000 --- a/src/pages/webcam/ui/webcam-map-page.tsx +++ /dev/null @@ -1,93 +0,0 @@ -'use client'; - -import { animated } from '@react-spring/web'; -import Image from 'next/image'; -import React from 'react'; - -import Summary from '@/widgets/weather/ui/summary'; -import useMapPinch from '@/widgets/webcam/hooks/useMapPinch'; -import type { Spot } from '@/entities/resort/model'; -import { ResortList } from '@/entities/resort/model'; -import LevelChip from '@/entities/slop/ui/level-chip'; -import { cn } from '@/shared/lib'; - -const WebCamMapPage = () => { - const [selectedTab, setSelectedTab] = React.useState(ResortList[0]); - const [selectedSpot, setSelectedSpot] = React.useState(null); - const { ref, style } = useMapPinch(); - - return ( -
- -
- {ResortList.map((tab) => ( -
{ - setSelectedTab(tab); - setSelectedSpot(null); - }} - > - {tab.name} -
- ))} -
- tab.name === selectedTab.name)!} /> -
- - {`${selectedTab.name}`} -
- selectedTab?.spots[0].isAvailable && setSelectedSpot(selectedTab.spots[0]) - } - /> - - {selectedSpot && ( -
-
- )} -
-
- {selectedTab.spots?.map((spot) => ( -
{ - spot.isAvailable && setSelectedSpot(spot); - }} - > -

{spot.name}

-
-

{spot.level}

-

헤라1,2

-
-
- ))} -
-
- ); -}; - -export default WebCamMapPage; diff --git a/src/pages/webcam/ui/webcam-mobile-map-page.tsx b/src/pages/webcam/ui/webcam-mobile-map-page.tsx index 8df5066..c6cc746 100644 --- a/src/pages/webcam/ui/webcam-mobile-map-page.tsx +++ b/src/pages/webcam/ui/webcam-mobile-map-page.tsx @@ -16,6 +16,7 @@ const WebCamMobileMapPage = () => { ...item, isWebcam: !!item.webcam, }))} + selectedSlop={selectedSlop} setSelectedSlop={setSelectedSlop} /> diff --git a/src/shared/icons/check.tsx b/src/shared/icons/check.tsx new file mode 100644 index 0000000..d346d04 --- /dev/null +++ b/src/shared/icons/check.tsx @@ -0,0 +1,30 @@ +import type { SVGProps } from 'react'; +import React from 'react'; + +interface CheckIconProps extends SVGProps { + className?: string; +} + +const CheckIcon = ({ className, ...props }: CheckIconProps) => { + return ( + + + + ); +}; + +export default CheckIcon; diff --git a/src/shared/icons/close.tsx b/src/shared/icons/close.tsx new file mode 100644 index 0000000..abe5652 --- /dev/null +++ b/src/shared/icons/close.tsx @@ -0,0 +1,29 @@ +import type { SVGProps } from 'react'; +import React from 'react'; + +interface CloseIconProps extends SVGProps { + className?: string; +} + +const CloseIcon = ({ className, ...args }: CloseIconProps) => { + return ( + + + + ); +}; + +export default CloseIcon; diff --git a/src/shared/lib/getBoundedPositions.ts b/src/shared/lib/getBoundedPositions.ts index ec4af9a..6f42d91 100644 --- a/src/shared/lib/getBoundedPositions.ts +++ b/src/shared/lib/getBoundedPositions.ts @@ -24,12 +24,7 @@ const getBoundedPositions = ( }, }; - const boundedPosition = { - x: Math.max(bounds.x.min, Math.min(bounds.x.max, position.x)), - y: Math.max(bounds.y.min, Math.min(bounds.y.max, position.y)), - }; - - return [boundedPosition.x, boundedPosition.y]; + return [bounds.x, bounds.y]; }; export default getBoundedPositions; diff --git a/src/shared/ui/close-button.tsx b/src/shared/ui/close-button.tsx new file mode 100644 index 0000000..2395357 --- /dev/null +++ b/src/shared/ui/close-button.tsx @@ -0,0 +1,26 @@ +import React, { forwardRef } from 'react'; +import CloseIcon from '../icons/close'; +import { cn } from '../lib'; + +type CloseButtonProps = React.ButtonHTMLAttributes; + +const CloseButton = forwardRef( + ({ className, ...args }, ref) => { + return ( + + ); + } +); + +CloseButton.displayName = 'CloseButton'; + +export default CloseButton; diff --git a/src/shared/ui/tooltip.tsx b/src/shared/ui/tooltip.tsx index 33661c8..f6cd58f 100644 --- a/src/shared/ui/tooltip.tsx +++ b/src/shared/ui/tooltip.tsx @@ -46,9 +46,11 @@ export const Tooltip = ({ trigger, children, isOpen }: TooltipProps) => { }} > {trigger} - - {children} - + + + {children} + + ); }; diff --git a/src/widgets/header/ui/slop-status-header.tsx b/src/widgets/header/ui/slop-status-header.tsx new file mode 100644 index 0000000..e93d04a --- /dev/null +++ b/src/widgets/header/ui/slop-status-header.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { cn } from '@/shared/lib'; + +const SlopStatusHeader = () => { + return ( +
+

슬로프 운행 현황

+

10월 21일 23:00 업데이트

+
+ ); +}; + +export default SlopStatusHeader; diff --git a/src/widgets/webcam/ui/webcam-map.tsx b/src/widgets/webcam/ui/webcam-map.tsx index 55fd55a..a918c73 100644 --- a/src/widgets/webcam/ui/webcam-map.tsx +++ b/src/widgets/webcam/ui/webcam-map.tsx @@ -1,14 +1,10 @@ -import { animated } from '@react-spring/web'; import type { StaticImageData } from 'next/image'; -import Image from 'next/image'; import type { ComponentType } from 'react'; import React, { useRef } from 'react'; +import SlopCamera from '@/features/slop/ui/slop-camera'; +import SlopMap from '@/features/slop/ui/slop-map'; import type { Level } from '@/entities/slop/model/model'; import { cn } from '@/shared/lib'; -import CameraButton from '@/shared/ui/cam-button'; - -import { Tooltip } from '@/shared/ui/tooltip'; -import useMapPinch from '../hooks/useMapPinch'; interface WebcamMapProps { slops: { @@ -24,6 +20,7 @@ interface WebcamMapProps { top: string; left: string; }; + src?: string; } | null; }[]; mapSrc: StaticImageData; @@ -32,48 +29,35 @@ interface WebcamMapProps { const WebcamMap = ({ slops, mapSrc, selectedSlop }: WebcamMapProps) => { const containerRef = useRef(null); - const { ref, style } = useMapPinch(containerRef); return (
- - 이미지 - {slops.map((slop) => { - return ( - -
- -
- {slop.webcam && ( -
- } isOpen={selectedSlop === slop.id}> -

{slop.webcam.name}

-
-
- )} -
- ); - })} -
+ {slops + .filter( + ( + slop + ): slop is WebcamMapProps['slops'][number] & { + webcam: NonNullable; + } => slop.webcam !== null + ) + .map(({ id, webcam }) => ( + + ))} +
); }; diff --git a/src/widgets/webcam/ui/webcam-slop-list.tsx b/src/widgets/webcam/ui/webcam-slop-list.tsx index cf804bc..6976421 100644 --- a/src/widgets/webcam/ui/webcam-slop-list.tsx +++ b/src/widgets/webcam/ui/webcam-slop-list.tsx @@ -13,12 +13,17 @@ interface WebcamSlopListProps { isOpen: boolean; isWebcam: boolean; }[]; + selectedSlop: string | null; setSelectedSlop: React.Dispatch>; } -const WebcamSlopList = ({ list, setSelectedSlop }: WebcamSlopListProps) => { +const WebcamSlopList = ({ list, selectedSlop, setSelectedSlop }: WebcamSlopListProps) => { const handleSlopClick = ({ id, isOpen }: { id: string; isOpen: boolean }) => { if (!isOpen) return; + if (selectedSlop === id) { + setSelectedSlop(null); + return; + } setSelectedSlop(id); }; return ( @@ -28,7 +33,8 @@ const WebcamSlopList = ({ list, setSelectedSlop }: WebcamSlopListProps) => {
  • handleSlopClick(item)} > diff --git a/tailwind.config.ts b/tailwind.config.ts index fc14ae2..2584159 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -19,6 +19,9 @@ const config = { }, extend: { + fontFamily: { + sans: ['Pretendard', 'sans-serif'], + }, colors: { border: 'hsl(var(--border))', input: 'hsl(var(--input))', diff --git a/yarn.lock b/yarn.lock index 05ca985..36f7632 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1555,6 +1555,11 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +eventemitter3@^4.0.3: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -1840,6 +1845,14 @@ hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: dependencies: function-bind "^1.1.2" +hls.js@^0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/hls.js/-/hls.js-0.14.17.tgz#0127cff2ec2f994a54eb955fe669ef6153a8e317" + integrity sha512-25A7+m6qqp6UVkuzUQ//VVh2EEOPYlOBg32ypr34bcPO7liBMOkKFvbjbCBfiPAOTA/7BSx1Dujft3Th57WyFg== + dependencies: + eventemitter3 "^4.0.3" + url-toolkit "^2.1.6" + ignore@^5.2.0, ignore@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" @@ -2624,6 +2637,13 @@ react-dom@^18: loose-envify "^1.1.0" scheduler "^0.23.2" +react-hls-player@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/react-hls-player/-/react-hls-player-3.0.7.tgz#1f66f55c2a5dc1fa6441ddc47b7c892079a276cf" + integrity sha512-i5QWNyLmaUhV/mgnpljRJT0CBfJnylClV/bne8aiXO3ZqU0+D3U/jtTDwdXM4i5qHhyFy9lemyZ179IgadKd0Q== + dependencies: + hls.js "^0.14.17" + react-is@^16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -3188,6 +3208,11 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +url-toolkit@^2.1.6: + version "2.2.5" + resolved "https://registry.yarnpkg.com/url-toolkit/-/url-toolkit-2.2.5.tgz#58406b18e12c58803e14624df5e374f638b0f607" + integrity sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg== + use-callback-ref@^1.3.0: version "1.3.2" resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.2.tgz#6134c7f6ff76e2be0b56c809b17a650c942b1693"