浏览代码

Merge branch 'release'

林艳 5 月之前
父节点
当前提交
9a49c529b7
共有 64 个文件被更改,包括 3173 次插入327 次删除
  1. 1 0
      content/order.js
  2. 321 0
      content/plus/videoPlayer/index-en-US.md
  3. 324 0
      content/plus/videoPlayer/index.md
  4. 3 0
      content/start/changelog/index-en-US.md
  5. 4 0
      content/start/changelog/index.md
  6. 2 1
      content/start/overview/index.md
  7. 58 0
      cypress/e2e/videoPlayer.spec.js
  8. 1 1
      lerna.json
  9. 3 3
      packages/semi-animation-react/package.json
  10. 1 1
      packages/semi-animation-styled/package.json
  11. 1 1
      packages/semi-animation/package.json
  12. 1 1
      packages/semi-eslint-plugin/package.json
  13. 3 3
      packages/semi-foundation/package.json
  14. 7 0
      packages/semi-foundation/videoPlayer/animation.scss
  15. 39 0
      packages/semi-foundation/videoPlayer/constants.ts
  16. 332 0
      packages/semi-foundation/videoPlayer/foundation.ts
  17. 136 0
      packages/semi-foundation/videoPlayer/progressFoundation.ts
  18. 75 0
      packages/semi-foundation/videoPlayer/variables.scss
  19. 323 0
      packages/semi-foundation/videoPlayer/videoPlayer.scss
  20. 1 1
      packages/semi-icons-lab/package.json
  21. 1 1
      packages/semi-icons/package.json
  22. 24 0
      packages/semi-icons/src/icons/IconMiniPlayer.tsx
  23. 1 0
      packages/semi-icons/src/icons/index.ts
  24. 1 1
      packages/semi-illustrations/package.json
  25. 1 1
      packages/semi-json-viewer-core/package.json
  26. 2 2
      packages/semi-next/package.json
  27. 1 1
      packages/semi-rspack/package.json
  28. 1 1
      packages/semi-scss-compile/package.json
  29. 1 1
      packages/semi-theme-default/package.json
  30. 1 0
      packages/semi-ui/index.ts
  31. 11 0
      packages/semi-ui/locale/interface.ts
  32. 11 0
      packages/semi-ui/locale/source/ar.ts
  33. 11 0
      packages/semi-ui/locale/source/de.ts
  34. 11 0
      packages/semi-ui/locale/source/en_GB.ts
  35. 11 0
      packages/semi-ui/locale/source/en_US.ts
  36. 11 0
      packages/semi-ui/locale/source/es.ts
  37. 11 0
      packages/semi-ui/locale/source/fr.ts
  38. 11 0
      packages/semi-ui/locale/source/id_ID.ts
  39. 11 0
      packages/semi-ui/locale/source/it.ts
  40. 11 0
      packages/semi-ui/locale/source/ja_JP.ts
  41. 11 0
      packages/semi-ui/locale/source/ko_KR.ts
  42. 11 0
      packages/semi-ui/locale/source/ms_MY.ts
  43. 11 0
      packages/semi-ui/locale/source/nl_NL.ts
  44. 11 0
      packages/semi-ui/locale/source/pl_PL.ts
  45. 11 0
      packages/semi-ui/locale/source/pt_BR.ts
  46. 11 0
      packages/semi-ui/locale/source/ro.ts
  47. 11 0
      packages/semi-ui/locale/source/ru_RU.ts
  48. 11 0
      packages/semi-ui/locale/source/sv_SE.ts
  49. 11 0
      packages/semi-ui/locale/source/th_TH.ts
  50. 11 0
      packages/semi-ui/locale/source/tr_TR.ts
  51. 11 0
      packages/semi-ui/locale/source/vi_VN.ts
  52. 11 0
      packages/semi-ui/locale/source/zh_CN.ts
  53. 11 0
      packages/semi-ui/locale/source/zh_TW.ts
  54. 7 7
      packages/semi-ui/package.json
  55. 1 1
      packages/semi-ui/tsconfig.json
  56. 15 0
      packages/semi-ui/videoPlayer/ErrorSvg.tsx
  57. 271 0
      packages/semi-ui/videoPlayer/_story/videoPlayer.stories.jsx
  58. 519 0
      packages/semi-ui/videoPlayer/index.tsx
  59. 15 0
      packages/semi-ui/videoPlayer/utils.ts
  60. 193 0
      packages/semi-ui/videoPlayer/videoProgress.tsx
  61. 1 1
      packages/semi-webpack/package.json
  62. 197 197
      sitemap.xml
  63. 3 0
      src/images/docIcons/doc-videoplayer.svg
  64. 28 101
      yarn.lock

+ 1 - 0
content/order.js

@@ -91,6 +91,7 @@ const order = [
     'locale',
     'jsonviewer',
     'audioPlayer',
+    'videoPlayer',
 ];
 let { exec } = require('child_process');
 let fs = require('fs');

+ 321 - 0
content/plus/videoPlayer/index-en-US.md

@@ -0,0 +1,321 @@
+---
+order: 93
+localeCode: en-US
+category: Plus
+title: VideoPlayer
+width: 60%
+icon: doc-videoplayer
+brief: Used to play video
+showNew: true
+---
+
+## Demos
+
+### How to import
+
+```jsx import
+import { VideoPlayer } from '@douyinfe/semi-ui';
+```
+
+### Basic Usage
+For basic usage, pass the video address through `src` and pass the video cover address through `poster`
+
+```jsx live=true dir="column"
+import React from 'react';
+import { VideoPlayer } from '@douyinfe/semi-ui';
+
+() => {
+    const src = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4";
+    const poster = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg";
+    return (
+        <VideoPlayer
+            height={630} 
+            src={src}
+            poster={poster}
+        />
+    );
+};
+```
+
+### Set controls list
+Set the display items of the menu bar through `controlsList`. The accepted value is an array. The default value is `['play', 'next', 'time', 'volume', 'playbackRate', 'quality', 'route', 'mirror', 'fullscreen', 'pictureInPicture']`
+
+```jsx live=true dir="column"
+import React from 'react';
+import { VideoPlayer } from '@douyinfe/semi-ui';
+
+() => {
+    const src = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4";
+    const poster = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg";
+    const controlsList = ['play', 'time', 'volume', 'playbackRate', 'fullscreen'];
+    return (
+        <VideoPlayer
+            height={630} 
+            src={src}
+            poster={poster}
+            controlsList={controlsList}
+        />
+    );
+};
+```
+
+### Loop playback
+Use `loop` to set loop playback
+```jsx live=true dir="column"
+import React from 'react';
+import { VideoPlayer } from '@douyinfe/semi-ui';
+
+() => {
+    const src = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4";
+    const poster = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg";
+    return (
+        <VideoPlayer 
+            height={630}
+            src={src}
+            poster={poster}
+            loop={true}
+        />
+    );
+};
+```
+
+### Fast forward and rewind
+Use `seekTime` to set the fast forward and rewind time, and use the left and right keys on the keyboard to fast forward and rewind.
+
+```jsx live=true dir="column"
+import React from 'react';
+import { VideoPlayer, Select } from '@douyinfe/semi-ui';
+
+() => {
+    const [seekTime, setSeekTime] = useState(5);
+
+    const src = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4";
+    const poster = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg";
+    return (
+        <div>
+            <span style={{ marginBottom: 10 }}>Please select the fast forward and rewind time</span>
+            <Select
+                value={seekTime}
+                style={{ width: 100, marginLeft: 10 }}
+                onChange={(value) => setSeekTime(value)}
+                optionList={[
+                    { label: '5s', value: 5 },
+                    { label: '10s', value: 10 },
+                    { label: '15s', value: 15 },
+                ]}
+                placeholder='Please select the fast forward and rewind time'
+            />
+            <VideoPlayer 
+                height={630}
+                style={{ marginTop: 10 }}
+                src={src}
+                poster={poster}
+                seekTime={seekTime}
+            />
+        </div>
+    );
+};
+```
+
+### Playback rate
+Set the rate selection list through `playbackRateList`
+
+```jsx live=true dir="column"
+import React from 'react';
+import { VideoPlayer } from '@douyinfe/semi-ui';
+
+() => {
+    const playbackRateList = [
+        { label: '0.5x', value: 0.5 },
+        { label: '1.0x', value: 1 },
+        { label: '1.5x', value: 1.5 },
+        { label: '2.0x', value: 2 },
+    ];
+    const src = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4";
+    const poster = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg";
+    return (
+        <VideoPlayer
+            height={630} 
+            src={src}
+            poster={poster}
+            playbackRateList={playbackRateList}
+        />
+    );
+};
+```
+
+### Volume setting
+Use `volume` to set the initial volume, the value range is 0 - 100, set `muted` to `true` to play silently
+
+```jsx live=true dir="column"
+import React from 'react';
+import { VideoPlayer } from '@douyinfe/semi-ui';
+
+() => {
+    const src = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4";
+    const poster = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg";
+    return (
+        <VideoPlayer
+            height={630} 
+            src={src}
+            poster={poster}
+            muted={true}
+        />
+    );
+};
+```
+
+### Clarity switching
+Use `qualityList` to set the clarity selection list, `defaultQuality` to set the initial selected clarity, and `onQualityChange` to set the `src` logic to be updated after clicking.
+
+Similarly for route switching, use `routeList` to set the clarity selection list, `defaultRoute` to set the initial selected route, and `onRouteChange` to set the `src` logic to be updated after clicking.
+
+```jsx live=true dir="column"
+import React from 'react';
+import { VideoPlayer } from '@douyinfe/semi-ui';
+
+() => {
+    const [src, setSrc] = useState('https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4');
+
+    const playList = [
+        {
+            src: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4',
+            quality: '1080p',
+        },
+        {
+            src: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/video/vchart-show-video-480p.mp4',
+            quality: '480p',
+        },
+    ];
+    const poster = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg";
+
+    const updateVideoSource = (quality) => {
+        const source = playList.find((item) => item.quality === quality);
+        setSrc(source.src);
+    };
+
+    return (
+        <VideoPlayer 
+            height={630}
+            src={src}
+            poster={poster}
+            defaultQuality={'1080p'}
+            qualityList={[
+                { label: '1080p', value: '1080p' },
+                { label: '480p', value: '480p' },
+            ]}
+            onQualityChange={(quality) => {
+                console.log('quality change', quality);
+                updateVideoSource(quality);
+            }}
+        />
+    );
+};
+```
+
+### Chapter markers
+Set chapter markers via `markers`
+
+```jsx live=true dir="column"
+import React from 'react';
+import { VideoPlayer } from '@douyinfe/semi-ui';
+
+() => {
+    const src = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4";
+    const poster = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg";
+    const markers = [
+        {
+            start: 0,
+            title: 'Start'
+        },
+        {
+            start: 4,
+            title: 'Function Introduction'
+        },
+        {
+            start: 38,
+            title: 'Figma Plugin'
+        },
+        {
+            start: 51,
+            title: 'Ending'
+        }
+    ];
+
+    return (
+        <VideoPlayer
+            height={630} 
+            src={src}
+            poster={poster}
+            markers={markers}
+        />
+    );
+};
+```
+
+### Theme
+Set the theme via `theme`, the theme only affects the background color
+
+```jsx live=true dir="column"
+import React from 'react';
+import { VideoPlayer } from '@douyinfe/semi-ui';
+
+() => {
+    const src = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4";
+    const poster = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg";
+    return (
+        <VideoPlayer 
+            height={630}
+            src={src}
+            poster={poster}
+            theme={'light'}
+        />
+    );
+};
+```
+
+### API
+
+| Properties | Description | Type | Default Value |
+|------------|---------------------------------------------|--------------------------------------|-------|
+| autoPlay   | Whether to play automatically                                  | boolean                              | false   |
+| captionsSrc   | captions source                                | string                              | -   |
+| className  | Class name                                         | string                               | -   |
+| clickToPlay | Whether to enable click to play                         | boolean                              | true   |
+| controlsList | Set the menu bar to display controls. All controls are displayed by default.                       | string[]                              | ['play', 'next', 'time', 'volume', 'playbackRate', 'quality', 'route', 'mirror', 'fullscreen', 'pictureInPicture']   |
+| crossOrigin | This enum attribute indicates whether CORS is used to fetch the video. CORS-enabled resources can be reused in 'canvas' elements without being polluted. Allowed values ​​are 'anonymous' and 'use-credentials' | 'anonymous' \|'use-credentials'   | - |
+| defaultPlaybackRate | Default playback rate                            | number                              | 1   |
+| defaultPlaybackRate | Default video resolution                       | string                              | -   |
+| defaultRoute | Default Line                       | string                              | -   |
+| height | height                       | string \| number                                    | -   |
+| loop | Whether to enable loop playback                                   | boolean                              | false   |
+| markers | Chapter marking                                 | Marker[]                              | -   |
+| muted | Whether to play silently                                   | boolean                              | false   |
+| onPause | Pause callback                                       | () => void                            | -   |
+| onPlay | Play callback                                       | () => void                            | -   |
+| onQualityChange | Switch quality callback                                       | (quality: string) => void                            | -   |
+| onRateChange | Switch rate callback                                               | (rate: number) => void                            | -   |
+| onRouteChange | Switch route callback                                                | (route: string) => void                            | -   |
+| onVolumeChange | Adjust volume callback                                       | (volume: number) => void                            | -   |
+| playbackRateList | Rate list, 6 playback rates are displayed by default, namely 0.5, 0.75, 1.0, 1.25, 1.5 and 2.0                     | Array<{ label: string; value: string }>                              | -   |
+| poster | Poster                       | string                              | -   |
+| qualityList | Quality list                      | Array<{ label: string; value: string }>                              | -   |
+| routeList | Route list                       | Array<{ label: string; value: string }>                              | -   |
+| seekTime | Fast forward and rewind time                   | number                              | 10   |
+| src | Video playback address                       | string                              | -   |
+| style | Style                                          | CSSProperties                        | - |
+| theme | Theme settings, different theme components have different background colors | 'dark' \| 'light' | 'dark' |
+| volume | Default volume                      | number                              | 100  |
+| width | width                   | string \| number                                | -   |
+
+
+#### Marker
+| Properties | Description | Type | Default Value |
+|------------|---------------------------------------------|--------------------------------------|-------|
+| start   | start time point                                  | number                              | 
+| title   | title                                  | string                              | 
+
+
+## Design Token
+
+<DesignToken/>

+ 324 - 0
content/plus/videoPlayer/index.md

@@ -0,0 +1,324 @@
+---
+order: 93
+localeCode: zh-CN
+category: Plus
+title: VideoPlayer 视频播放器
+width: 60%
+icon: doc-videoplayer
+brief: 用于播放视频
+showNew: true
+---
+
+## 代码演示
+
+### 如何引入
+
+```jsx import
+import { VideoPlayer } from '@douyinfe/semi-ui';
+```
+
+### 基本用法
+基本使用,通过 `src` 传入视频地址, 通过 `poster` 传入视频封面地址
+
+```jsx live=true dir="column"
+import React from 'react';
+import { VideoPlayer } from '@douyinfe/semi-ui';
+
+() => {
+    const src = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4";
+    const poster = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg";
+    return (
+        <VideoPlayer
+            height={630} 
+            src={src}
+            poster={poster}
+        />
+    );
+};
+```
+
+### 设置菜单栏功能
+通过 `controlsList` 设置菜单栏的展示项,该项接受值为数组,默认值为`['play', 'next', 'time', 'volume', 'playbackRate', 'quality', 'route', 'mirror', 'fullscreen', 'pictureInPicture']`
+
+```jsx live=true dir="column"
+import React from 'react';
+import { VideoPlayer } from '@douyinfe/semi-ui';
+
+() => {
+    const src = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4";
+    const poster = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg";
+    const controlsList = ['play', 'time', 'volume', 'playbackRate', 'fullscreen'];
+    return (
+        <VideoPlayer
+            height={630} 
+            src={src}
+            poster={poster}
+            controlsList={controlsList}
+        />
+    );
+};
+```
+
+### 循环播放
+通过 `loop` 设置循环播放
+
+```jsx live=true dir="column"
+import React from 'react';
+import { VideoPlayer } from '@douyinfe/semi-ui';
+
+() => {
+    const src = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4";
+    const poster = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg";
+    return (
+        <VideoPlayer 
+            height={630}
+            src={src}
+            poster={poster}
+            loop={true}
+        />
+    );
+};
+```
+
+### 快进快退
+通过 `seekTime` 设置快进快退时间,通过键盘左右键执行快进快退
+
+```jsx live=true dir="column"
+import React from 'react';
+import { VideoPlayer, Select } from '@douyinfe/semi-ui';
+
+() => {
+    const [seekTime, setSeekTime] = useState(5);
+
+    const src = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4";
+    const poster = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg";
+    return (
+        <div>
+            <div>
+                <span style={{ marginBottom: 10 }}>请选择快进快退时间</span>
+                <Select
+                    value={seekTime}
+                    style={{ width: 100, marginLeft: 10 }}
+                    onChange={(value) => setSeekTime(value)}
+                    optionList={[
+                        { label: '5s', value: 5 },
+                        { label: '10s', value: 10 },
+                        { label: '15s', value: 15 },
+                    ]}
+                    placeholder='请选择快进快退时间'
+                />
+            </div>
+            <VideoPlayer 
+                height={630}
+                style={{ marginTop: 10 }}
+                src={src}
+                poster={poster}
+                seekTime={seekTime}
+            />
+        </div>
+    );
+};
+```
+
+### 播放速率
+通过 `playbackRateList` 设置速率选择列表
+
+```jsx live=true dir="column"
+import React from 'react';
+import { VideoPlayer } from '@douyinfe/semi-ui';
+
+() => {
+    const playbackRateList = [
+        { label: '0.5x', value: 0.5 },
+        { label: '1.0x', value: 1 },
+        { label: '1.5x', value: 1.5 },
+        { label: '2.0x', value: 2 },
+    ];
+    const src = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4";
+    const poster = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg";
+    return (
+        <VideoPlayer
+            height={630} 
+            src={src}
+            poster={poster}
+            playbackRateList={playbackRateList}
+        />
+    );
+};
+```
+
+### 音量设置
+通过 `volume` 设置初始音量,值区间为 0 - 100, 设置 `muted` 为 `true` 可以静音播放
+
+```jsx live=true dir="column"
+import React from 'react';
+import { VideoPlayer } from '@douyinfe/semi-ui';
+
+() => {
+    const src = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4";
+    const poster = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg";
+    return (
+        <VideoPlayer
+            height={630} 
+            src={src}
+            poster={poster}
+            muted={true}
+        />
+    );
+};
+```
+
+### 清晰度切换
+通过 `qualityList` 设置清晰度选择列表,`defaultQuality` 设置初始选择的清晰度,`onQualityChange` 设置点击后更新的 `src` 逻辑。
+
+线路切换同理,通过 `routeList` 设置清晰度选择列表,`defaultRoute` 设置初始选择的线路,`onRouteChange` 设置点击后更新的 `src` 逻辑。
+
+```jsx live=true dir="column"
+import React from 'react';
+import { VideoPlayer } from '@douyinfe/semi-ui';
+
+() => {
+    const [src, setSrc] = useState('https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4');
+
+    const playList = [
+        {
+            src: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4',
+            quality: '1080p',
+        },
+        {
+            src: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/video/vchart-show-video-480p.mp4',
+            quality: '480p',
+        },
+    ];
+    const poster = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg";
+
+    const updateVideoSource = (quality) => {
+        const source = playList.find((item) => item.quality === quality);
+        setSrc(source.src);
+    };
+
+    return (
+        <VideoPlayer 
+            height={630}
+            src={src}
+            poster={poster}
+            defaultQuality={'1080p'}
+            qualityList={[
+                { label: '1080p', value: '1080p' },
+                { label: '480p', value: '480p' },
+            ]}
+            onQualityChange={(quality) => {
+                console.log('quality change', quality);
+                updateVideoSource(quality);
+            }}
+        />
+    );
+};
+```
+
+### 章节标记
+通过 `markers` 设置章节标记点
+
+```jsx live=true dir="column"
+import React from 'react';
+import { VideoPlayer } from '@douyinfe/semi-ui';
+
+() => {
+    const src = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4";
+    const poster = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg";
+    const markers = [
+        {
+            start: 0,
+            title: '片头'
+        },
+        {
+            start: 4,
+            title: '功能介绍'
+        },
+        {
+            start: 38,
+            title: 'Figma Plugin'
+        },
+        {
+            start: 51,
+            title: '片尾'
+        }
+    ];
+
+    return (
+        <VideoPlayer
+            height={630} 
+            src={src}
+            poster={poster}
+            markers={markers}
+        />
+    );
+};
+```
+
+### 主题
+通过 `theme` 设置主题, 主题仅影响背景色
+
+```jsx live=true dir="column"
+import React from 'react';
+import { VideoPlayer } from '@douyinfe/semi-ui';
+
+() => {
+    const src = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4";
+    const poster = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg";
+    return (
+        <VideoPlayer 
+            src={src}
+            poster={poster}
+            height={630}
+            theme={'light'}
+        />
+    );
+};
+```
+
+### API
+
+| 属性        | 说明                                        | 类型                                  | 默认值   |
+|------------|---------------------------------------------|--------------------------------------|-------|
+| autoPlay   | 是否自动播放                                  | boolean                              | false   |
+| captionsSrc   | 字幕资源                                  | string                              | -   |
+| className  | 类名                                         | string                               | -   |
+| clickToPlay | 是否启用点击以播放                            | boolean                              | true   |
+| controlsList | 设置菜单栏展示控件,默认展示所有控件                       | string[]                              | ['play', 'next', 'time', 'volume', 'playbackRate', 'quality', 'route', 'mirror', 'fullscreen', 'pictureInPicture']   |
+| crossOrigin | 该枚举属性指明是否使用 CORS 来获取相关视频。允许 CORS 的资源可在 'canvas' 元素中被重用,而不会被污染。允许的值有 'anonymous' 和 'use-credentials'  | 'anonymous' \|'use-credentials'                                | -  |
+| defaultPlaybackRate | 默认倍率                            | number                              | 1   |
+| defaultPlaybackRate | 默认视频清晰度                       | string                              | -   |
+| defaultRoute | 默认线路                       | string                              | -   |
+| height | 高度                       | string \| number                                    | -   |
+| loop | 是否启用循环播放                                   | boolean                              | false   |
+| markers | 节点标记                                 | Marker[]                              | -   |
+| muted | 是否静音播放                                   | boolean                              | false   |
+| onPause | 暂停回调                                       | () => void                            | -   |
+| onPlay | 播放回调                                       | () => void                            | -   |
+| onQualityChange | 切换清晰度回调                                       | (quality: string) => void                            | -   |
+| onRateChange | 切换速率回调                                       | (rate: number) => void                            | -   |
+| onRouteChange | 切换线路回调                                       | (route: string) => void                            | -   |
+| onVolumeChange | 调整音量回调                                       | (volume: number) => void                            | -   |
+| playbackRateList | 速率列表,默认展示 6 种播放速率,分别为 0.5,0.75,1.0,1.25,1.5 和 2.0                      | Array<{ label: string; value: string }>                              | -   |
+| poster | 封面图                       | string                              | -   |
+| qualityList | 清晰度列表                      | Array<{ label: string; value: string }>                              | -   |
+| routeList | 线路列表                       | Array<{ label: string; value: string }>                              | -   |
+| seekTime | 快进快退时间                   | number                              | 10   |
+| src | 视频播放地址                       | string                              | -   |
+| style | 样式                                          | CSSProperties                        | - |
+| theme | 主题设置,不同主题组件的背景色不同                 | 'dark' \| 'light'                        | 'dark' |
+| volume | 默认音量                      | number                              | 100  |
+| width | 宽度                   | string \| number                                | -   |
+
+
+#### Marker
+| 属性        | 说明                                        | 类型                                  | 默认值   |
+|------------|---------------------------------------------|--------------------------------------|-------|
+| start   | 起始时间点                                  | number                              | 
+| title   | 标题                                  | string                              | 
+
+
+## 设计变量
+
+<DesignToken/>

+ 3 - 0
content/start/changelog/index-en-US.md

@@ -15,6 +15,9 @@ Version:Major.Minor.Patch (follow the **Semver** specification)
 -   **Patch version**: Only include bug fix, the release time is not limited
 
 ---
+#### 🎉 2.80.0-beta.0 (2025-05-14)
+- 【Feat】
+    - add VideoPlayer component support
 
 #### 🎉 2.79.0 (2025-05-08)
 - 【Feat】

+ 4 - 0
content/start/changelog/index.md

@@ -13,6 +13,10 @@ Semi 版本号遵循 **Semver** 规范(主版本号 - 次版本号 - 修订版
 -   修订版本号(patch):仅会进行 bugfix,发布时间不限
 -   不同版本间的详细关系,可查阅 [FAQ](/zh-CN/start/faq)
 
+#### 🎉 2.80.0-beta.0 (2025-05-14)
+- 【Feat】
+    - 新增 VideoPlayer 组件 [#2822](https://github.com/DouyinFE/semi-design/pull/2822)
+
 #### 🎉 2.79.0 (2025-05-08)
 - 【Feat】
     - Upload 添加文件名超长时弹出文件名提示功能 [@yatbfm](https://github.com/yatbfm) [#2753](https://github.com/DouyinFE/semi-design/pull/2753)

+ 2 - 1
content/start/overview/index.md

@@ -28,7 +28,8 @@ Chat 对话,
 HotKeys 快捷键,
 DragMove 拖拽移动,
 JsonViewer Json编辑器,
-AudioPlayer 音频播放器
+AudioPlayer 音频播放器,
+VideoPlayer 视频播放器
 ```
 
 ## 输入类

+ 58 - 0
cypress/e2e/videoPlayer.spec.js

@@ -0,0 +1,58 @@
+describe('videoPlayer', () => {
+
+    it('basic element check', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=videoplayer--basic');
+        cy.get('.semi-videoPlayer-poster').should('exist');
+        cy.get('.semi-videoPlayer-wrapper-dark').should('exist');
+        cy.get('.semi-videoPlayer-error').should('not.exist');
+        cy.get('.semi-videoPlayer-pause').should('exist');
+        cy.get('img').should('have.attr', 'src', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg');
+        cy.get('video').should('have.attr', 'src', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4');
+    });
+    
+    it('control list', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=videoplayer--control-list');
+        cy.get('.semi-icon-flip-horizontal').should('not.exist');
+        cy.get('.semi-icon-mini_player').should('not.exist');
+    });
+
+    it('theme', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=videoplayer--theme');
+        cy.get('.semi-videoPlayer-wrapper-dark').should('exist');
+        cy.get('.semi-videoPlayer-wrapper-light').should('exist');
+    });
+
+    it('set seek time', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=videoplayer--set-seek-time');
+        cy.wait(500);
+        cy.get('body').type('{rightArrow}');
+        cy.get('.semi-videoPlayer-controls-time').contains('00:05').should('exist');
+    });
+
+    it('set play list', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=videoplayer--playback-rate-list');
+        cy.get('.semi-videoPlayer-controls-menu-item').contains('1.0x').trigger('mouseover');
+        cy.get('.semi-videoPlayer-controls-popup-menu-item').contains('1.0x').should('exist');
+        cy.get('.semi-videoPlayer-controls-popup-menu-item').contains('1.5x').should('exist');
+        cy.get('.semi-videoPlayer-controls-popup-menu-item').contains('2.0x').should('exist');
+        cy.get('.semi-videoPlayer-controls-popup-menu-item').contains('1.25x').should('not.exist');
+        cy.get('.semi-videoPlayer-controls-popup-menu-item').contains('0.75x').should('not.exist');
+    });
+
+    it('volume', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=videoplayer--volume');
+        cy.get('.semi-icon-mute').should('exist');
+    });
+
+    it('video load error', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=videoplayer--no-resource');
+        cy.get('.semi-videoPlayer').contains('视频加载错误').should('exist');
+    });
+
+    it('chapter', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=videoplayer--chapter');
+        cy.get('.semi-videoPlayer-progress-slider').should('have.length', 4);
+        cy.get('.semi-videoPlayer-progress-slider').eq(1).trigger('mouseover');
+        cy.get('.semi-tooltip-content').contains('功能介绍').should('exist');
+    });
+});

+ 1 - 1
lerna.json

@@ -1,5 +1,5 @@
 {
     "useWorkspaces": true,
     "npmClient": "yarn",
-    "version": "2.79.0"
+    "version": "2.80.0-beta.0"
 }

+ 3 - 3
packages/semi-animation-react/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-animation-react",
-    "version": "2.79.0",
+    "version": "2.80.0-beta.0",
     "description": "motion library for semi-ui-react",
     "keywords": [
         "motion",
@@ -25,8 +25,8 @@
         "prepublishOnly": "npm run build:lib"
     },
     "dependencies": {
-        "@douyinfe/semi-animation": "2.79.0",
-        "@douyinfe/semi-animation-styled": "2.79.0",
+        "@douyinfe/semi-animation": "2.80.0-beta.0",
+        "@douyinfe/semi-animation-styled": "2.80.0-beta.0",
         "classnames": "^2.2.6"
     },
     "devDependencies": {

+ 1 - 1
packages/semi-animation-styled/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-animation-styled",
-    "version": "2.79.0",
+    "version": "2.80.0-beta.0",
     "description": "semi styled animation",
     "keywords": [
         "semi",

+ 1 - 1
packages/semi-animation/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-animation",
-    "version": "2.79.0",
+    "version": "2.80.0-beta.0",
     "description": "animation base library for semi-ui",
     "keywords": [
         "animation",

+ 1 - 1
packages/semi-eslint-plugin/package.json

@@ -1,6 +1,6 @@
 {
     "name": "eslint-plugin-semi-design",
-    "version": "2.79.0",
+    "version": "2.80.0-beta.0",
     "description": "semi ui eslint plugin",
     "keywords": [
         "semi",

+ 3 - 3
packages/semi-foundation/package.json

@@ -1,14 +1,14 @@
 {
     "name": "@douyinfe/semi-foundation",
-    "version": "2.79.0",
+    "version": "2.80.0-beta.0",
     "description": "",
     "scripts": {
         "build:lib": "node ./scripts/compileLib.js",
         "prepublishOnly": "npm run build:lib"
     },
     "dependencies": {
-        "@douyinfe/semi-animation": "2.79.0",
-        "@douyinfe/semi-json-viewer-core": "2.79.0",
+        "@douyinfe/semi-animation": "2.80.0-beta.0",
+        "@douyinfe/semi-json-viewer-core": "2.80.0-beta.0",
         "@mdx-js/mdx": "^3.0.1",
         "async-validator": "^3.5.0",
         "classnames": "^2.2.6",

+ 7 - 0
packages/semi-foundation/videoPlayer/animation.scss

@@ -0,0 +1,7 @@
+$animation_duration-videoPlayer_controls-show: 500ms; // videoPlayer菜单栏-弹出动画-动画持续时间
+$animation_duration-videoPlayer_slider-in: 300ms; // videoPlayer进度条-弹出动画-动画持续时间
+$animation_duration-videoPlayer_slider-out: 300ms; // videoPlayer进度条-收起动画-动画持续时间
+
+
+$animation_function-videoPlayer_slider-in: cubic-bezier(0.62, 0.05, 0.36, 0.95); // videoPlayer进度条-弹出动画-动画插值函数
+$animation_function-videoPlayer_slider-out: cubic-bezier(0.62, 0.05, 0.36, 0.95); // videoPlayer进度条-收起动画-动画插值函数

+ 39 - 0
packages/semi-foundation/videoPlayer/constants.ts

@@ -0,0 +1,39 @@
+import { BASE_CLASS_PREFIX } from '../base/constants';
+
+const cssClasses = {
+    PREFIX: `${BASE_CLASS_PREFIX}-videoPlayer`,
+    PREFIX_CONTROLS: `${BASE_CLASS_PREFIX}-videoPlayer-controls`,
+    PREFIX_PROGRESS: `${BASE_CLASS_PREFIX}-videoPlayer-progress`,
+} as const;
+
+const strings = {
+    DARK: 'dark',
+    LIGHT: 'light',
+    PLAY: 'play',
+    NEXT: 'next',
+    TIME: 'time',
+    VOLUME: 'volume',
+    PLAYBACK_RATE: 'playbackRate',
+    QUALITY: 'quality',
+    ROUTE: 'route',
+    MIRROR: 'mirror',
+    FULLSCREEN: 'fullscreen',
+    PICTURE_IN_PICTURE: 'pictureInPicture',
+} as const;
+
+const numbers = {
+    DEFAULT_VOLUME: 100,
+    DEFAULT_SEEK_TIME: 10,
+    DEFAULT_VOLUME_STEP: 10,
+    DEFAULT_PLAYBACK_RATE: 1,
+} as const;
+
+const DEFAULT_PLAYBACK_RATE = [
+    { label: '2.0x', value: 2 },
+    { label: '1.5x', value: 1.5 },
+    { label: '1.25x', value: 1.25 },
+    { label: '1.0x', value: 1 },
+    { label: '0.75x', value: 0.75 },
+];
+
+export { cssClasses, strings, numbers, DEFAULT_PLAYBACK_RATE }; 

+ 332 - 0
packages/semi-foundation/videoPlayer/foundation.ts

@@ -0,0 +1,332 @@
+import BaseFoundation, { DefaultAdapter } from '../base/foundation';
+import { numbers } from './constants';
+import { throttle } from 'lodash';
+
+export interface VideoPlayerAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
+    getVideo: () => HTMLVideoElement | null;
+    getVideoWrapper: () => HTMLDivElement | null;
+    notifyPause: () => void;
+    notifyPlay: () => void;
+    notifyQualityChange: (quality: string) => void;
+    notifyRateChange: (rate: number) => void;
+    notifyRouteChange: (route: string) => void;
+    notifyVolumeChange: (volume: number) => void;
+    setBufferedValue: (bufferedValue: number) => void;
+    setCurrentTime: (currentTime: number) => void;
+    setIsError: (isError: boolean) => void;
+    setIsMirror: (isMirror: boolean) => void;
+    setIsPlaying: (isPlaying: boolean) => void;
+    setMuted: (muted: boolean) => void;
+    setNotificationContent: (content: string) => void;
+    setPlaybackRate: (rate: number) => void;
+    setQuality: (quality: string) => void;
+    setRoute: (route: string) => void;
+    setShowControls: (showControls: boolean) => void;
+    setShowNotification: (showNotification: boolean) => void;
+    setTotalTime: (totalTime: number) => void;
+    setVolume: (volume: number) => void
+}
+
+export default class VideoPlayerFoundation<P = Record<string, any>, S = Record<string, any>> extends BaseFoundation<VideoPlayerAdapter<P, S>, P, S> {
+    constructor(adapter: VideoPlayerAdapter<P, S>) {
+        super({ ...adapter });
+    }
+
+    private controlsTimer: NodeJS.Timeout | null;
+    private scrollPosition: { x: number; y: number } | null = null;
+
+    init() {
+        const { volume, muted } = this.getProps();
+        const video = this._adapter.getVideo();
+        if (video) {
+            this._adapter.setTotalTime(video.duration);
+            this.handleVolumeChange(muted ? 0 : volume);
+        }
+        this.registerEvent();
+    }
+
+    destroy() {
+        this.unregisterEvent();
+        this.clearTimer();
+    }
+
+    shouldShowControlItem(name: string) {
+        const { controlsList } = this.getProps();
+        if (controlsList.includes(name)) {
+            return true;
+        }
+        return false;
+    }
+
+    clearTimer() {
+        if (this.controlsTimer) {
+            clearTimeout(this.controlsTimer);
+        }
+    }
+
+    handleMouseMove = throttle(() => {
+        this._adapter.setShowControls(true);
+        this.clearTimer();
+        this.controlsTimer = setTimeout(() => {
+            this._adapter.setShowControls(false);
+        }, 3000);
+    }, 200);
+
+
+    handleTimeChange(value: number) {
+        const video = this._adapter.getVideo();
+        if (!video) return;
+        if (!Number.isNaN(value)) {
+            video.currentTime = value;
+            this._adapter.setCurrentTime(value);
+        }
+    }
+
+    handleTimeUpdate() {
+        const video = this._adapter.getVideo();
+        if (!video) return;
+        this._adapter.setCurrentTime(video.currentTime);
+    }
+
+    handleDurationChange() {
+        const video = this._adapter.getVideo();
+        if (!video) return;
+        this._adapter.setTotalTime(video.duration);
+    }
+
+    handleError() {
+        this._adapter.setIsError(true);
+    }
+
+    handlePlayOrPause() {
+        const video = this._adapter.getVideo();
+        if (!video) return;
+        video.paused ? this.handlePlay() : this.handlePause();
+    }
+
+    handlePlay() {
+        const video = this._adapter.getVideo();
+        if (video) {
+            video.play();
+            this._adapter.setIsPlaying(true);
+            this._adapter.notifyPlay();
+        }
+    }
+
+    handlePause() {
+        const video = this._adapter.getVideo();
+        if (video) {
+            video.pause();
+            this._adapter.setIsPlaying(false);
+            this._adapter.notifyPause();
+        }
+    }
+
+    handleCanPlay = () => {
+        this._adapter.setShowNotification(false);
+    }
+
+    handleWaiting = (locale: any) => {
+        this._adapter.setNotificationContent(locale.loading);
+        this._adapter.setShowNotification(true);
+    }
+
+    handleStalled = (locale: any) => {
+        this._adapter.setNotificationContent(locale.stall);
+        this._adapter.setShowNotification(true);
+    }
+
+    handleProgress = () => {
+        const video = this._adapter.getVideo();
+        if (video && video.buffered.length > 0) {
+            const bufferedEnd = video.buffered.end(video.buffered.length - 1);
+            this._adapter.setBufferedValue(bufferedEnd);
+        }
+    }
+
+    handleEnded = () => {
+        this._adapter.setIsPlaying(false);
+        this._adapter.setShowControls(true);
+    }
+
+    handleVolumeChange(value: number) {
+        const video = this._adapter.getVideo();
+        if (!video) return;
+        const volume = Math.floor(value > 0 ? value : 0);
+        video.volume = volume / 100;
+        this._adapter.setVolume(volume);
+        this._adapter.setMuted(volume === 0 ? true : false);
+    }
+
+    handleVolumeSilent = () => {
+        const video = this._adapter.getVideo();
+        const { volume, muted } = this.getStates();
+        if (!video) return;
+        if (muted) {
+            video.volume = volume / 100;
+            this._adapter.setVolume(volume);
+            this._adapter.setMuted(false);
+        } else {
+            video.volume = 0;
+            this._adapter.setMuted(true);
+        }
+    }
+
+    checkFullScreen() {
+        const videoWrapper = this._adapter.getVideoWrapper();
+        if (!videoWrapper) return false;
+        return !!(
+            document.fullscreenElement === videoWrapper ||
+            // @ts-ignore
+            document?.webkitFullscreenElement === videoWrapper ||
+            // @ts-ignore
+            document?.mozFullScreenElement === videoWrapper ||
+            // @ts-ignore
+            document?.msFullscreenElement === videoWrapper ||
+            // @ts-ignore
+            videoWrapper?.webkitDisplayingFullscreen  // iOS Safari 特殊处理
+        );
+    }
+
+    handleFullscreen = () => {
+        const videoWrapper = this._adapter.getVideoWrapper();
+        const isFullScreen = this.checkFullScreen();
+        if (videoWrapper) {
+            if (isFullScreen) {
+                document.exitFullscreen();
+            } else {
+                // record scroll position before entering fullscreen
+                this.scrollPosition = {
+                    x: window.scrollX,
+                    y: window.scrollY
+                };
+                videoWrapper.requestFullscreen();
+            }
+        }
+    }
+
+    handleRateChange(rate: { label: string; value: number }, locale: any) {
+        const video = this._adapter.getVideo();
+        if (!video) return;
+        video.playbackRate = rate.value;
+        this._adapter.setPlaybackRate(rate.value);
+        this._adapter.notifyRateChange(rate.value);
+        this.handleTemporaryNotification(locale.rateChange.replace('${rate}', rate.label));
+    }
+
+    handleQualityChange(quality: { label: string; value: string }, locale: any) {
+        this._adapter.setQuality(quality.value);
+        this._adapter.notifyQualityChange(quality.value);
+        this.handleTemporaryNotification(locale.qualityChange.replace('${quality}', quality.label));
+        this.restorePlayPosition();
+    }
+
+    handleRouteChange(route: { label: string; value: string }, locale: any) {
+        this._adapter.setRoute(route.value);
+        this._adapter.notifyRouteChange?.(route.value);
+        this.handleTemporaryNotification(locale.routeChange.replace('${route}', route.label));
+        this.restorePlayPosition();
+    }
+
+    handleMirror = (locale: any) => {
+        const { isMirror } = this.getStates();
+        this._adapter.setIsMirror(!isMirror);
+        this.handleTemporaryNotification(!isMirror ? locale.mirror : locale.cancelMirror);
+    }
+
+    handlePictureInPicture = () => {
+        const video = this._adapter.getVideo();
+        if (!video) return;
+        video.requestPictureInPicture();
+    }
+
+    handleLeavePictureInPicture = () => {
+        const video = this._adapter.getVideo();
+        if (!video) return;
+        this._adapter.setIsPlaying(!video.paused);
+    };
+
+    handleTemporaryNotification = (content: string) => {
+        this._adapter.setNotificationContent(content);
+        this._adapter.setShowNotification(true);
+        setTimeout(() => {
+            this._adapter.setShowNotification(false);
+        }, 1000);
+    }
+
+    restorePlayPosition() {
+        const video = this._adapter.getVideo();
+        if (!video) return;
+        const wasPlaying = !video.paused;
+        const currentTime = video.currentTime;
+
+        const handleLoaded = () => {
+            video.currentTime = currentTime;
+            if (wasPlaying) {
+                video.play();
+            }
+            video.removeEventListener('loadeddata', handleLoaded);
+        };
+        video.addEventListener('loadeddata', handleLoaded);
+    }
+
+    handleMouseEnterWrapper = () => {
+        this._adapter.setShowControls(true);
+    }
+
+    handleMouseLeaveWrapper = () => {
+        const { isPlaying } = this.getStates();
+        if (isPlaying) {
+            this._adapter.setShowControls(false);
+        }
+    }
+
+    handleFullscreenChange = () => {
+        const isFullScreen = this.checkFullScreen();
+        if (isFullScreen) {
+            document.addEventListener('mousemove', this.handleMouseMove);
+        } else {
+            // according to the exit fullScreen has two way, Esc && click the button
+            // so we need to restore scroll position after exiting fullscreen
+            if (this.scrollPosition) {
+                setTimeout(() => {
+                    window.scrollTo(this.scrollPosition.x, this.scrollPosition.y);
+                    this.scrollPosition = null;
+                }, 0);
+            }
+            document.removeEventListener('mousemove', this.handleMouseMove);
+        }
+    }
+
+    registerEvent = () => {
+        const video = this._adapter.getVideo();
+        if (!video) return;
+        document.addEventListener('keydown', (e) => this.handleBodyKeyDown(e));
+        document.addEventListener('fullscreenchange', this.handleFullscreenChange);
+        video.addEventListener('leavepictureinpicture', this.handleLeavePictureInPicture);
+    }
+
+    unregisterEvent = () => {
+        const video = this._adapter.getVideo();
+        if (!video) return;
+        document.removeEventListener('keydown', (e) => this.handleBodyKeyDown(e));
+        document.removeEventListener('fullscreenchange', this.handleFullscreenChange);
+        video.removeEventListener('leavepictureinpicture', this.handleLeavePictureInPicture);
+    }
+
+    handleBodyKeyDown(e: KeyboardEvent) {
+        const { currentTime, volume } = this.getStates();
+        const { seekTime } = this.getProps();
+        if (e.key === ' ') {
+            this.handlePlayOrPause();
+        // } else if (e.key === 'ArrowUp') {
+        //     this.handleVolumeChange(volume + numbers.DEFAULT_VOLUME_STEP);
+        // } else if (e.key === 'ArrowDown') { 
+        //     this.handleVolumeChange(volume - numbers.DEFAULT_VOLUME_STEP);   
+        } else if (e.key === 'ArrowLeft') {
+            this.handleTimeChange(currentTime - seekTime);
+        } else if (e.key === 'ArrowRight') {
+            this.handleTimeChange(currentTime + seekTime);
+        }
+    }
+} 

+ 136 - 0
packages/semi-foundation/videoPlayer/progressFoundation.ts

@@ -0,0 +1,136 @@
+import BaseFoundation, { DefaultAdapter } from '../base/foundation';
+
+export interface MarkerListItem {
+    start: number;
+    end: number;
+    title: string;
+    width: string;
+    left: string
+}
+
+export interface Marker {
+    start: number;
+    title: string
+}
+
+export interface VideoProgressAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
+    getSliderRef: () => HTMLDivElement | null;
+    getMarkersList: () => MarkerListItem[];
+    setIsDragging: (isDragging: boolean) => void;
+    setIsHandleHovering: (isHandleHovering: boolean) => void;
+    setActiveIndex: (activeIndex: number) => void;
+    setMovingInfo: (movingInfo: { progress: number; offset: number; value: number } | null) => void
+}
+
+export default class VideoProgressFoundation<P = Record<string, any>, S = Record<string, any>> extends BaseFoundation<VideoProgressAdapter<P, S>, P, S> {
+    constructor(adapter: VideoProgressAdapter<P, S>) {
+        super({ ...adapter });
+    }
+
+    handleDocumentMouseMove = (e: MouseEvent) => {
+        const { isDragging } = this.getStates();
+        if (isDragging) {
+            this.handleMouseEvent(e, true);
+        }
+    };
+
+    handleDocumentMouseUp = () => {
+        const { isDragging } = this.getStates();
+        if (isDragging) {
+            this._adapter.setIsDragging(false);
+        }
+        document.removeEventListener('mousemove', this.handleDocumentMouseMove);
+        document.removeEventListener('mouseup', this.handleDocumentMouseUp);
+    };
+
+    handleMouseDown = (e: any) => {
+        this._adapter.setIsDragging(true);
+        this.handleMouseEvent(e, true);
+        document.addEventListener('mousemove', this.handleDocumentMouseMove);
+        document.addEventListener('mouseup', this.handleDocumentMouseUp);
+    };
+
+    handleMouseUp = () => {
+        const { isDragging } = this.getStates();
+        if (isDragging) {
+            this._adapter.setIsDragging(false);
+        }
+    };
+
+    handleMouseEvent = (e: any, shouldSetValue: boolean = true) => {
+        const { isDragging } = this.getStates();
+        const { onChange, max } = this.getProps();
+        const sliderRef = this._adapter.getSliderRef();
+        if (!sliderRef) return;
+        const rect = sliderRef.getBoundingClientRect();
+        const offset = (e.clientX - rect.left);
+        const total = rect.width;
+        const percentage = Math.min(Math.max(offset / total, 0), 1);
+        const value = percentage * max;
+        
+        if (shouldSetValue && (isDragging || e.type === 'mousedown')) {
+            this.setActiveIndex(value);
+            onChange(value);
+        }
+
+        this._adapter.setMovingInfo({
+            progress: percentage,
+            offset: offset - rect.width / 2,
+            value
+        });
+    };
+
+    handleSliderMouseEnter = (index: number) => {
+        const { value: currentValue } = this.getProps();
+        const markersList = this._adapter.getMarkersList();
+        const currentSlider = markersList[index];
+        if (currentSlider.start < currentValue && currentSlider.end > currentValue) {
+            this._adapter.setIsHandleHovering(true);
+        } else {
+            this._adapter.setIsHandleHovering(false);
+        }
+    }
+
+    handleSliderMouseLeave = (index: number) => {
+        const { value: currentValue } = this.getProps();
+        const markersList = this._adapter.getMarkersList();
+        const currentSlider = markersList[index];
+        if (currentSlider.start < currentValue && currentSlider.end > currentValue) {
+            this._adapter.setIsHandleHovering(false);
+        }
+    }
+
+    setActiveIndex = (currentValue: number) => {
+        const markersList = this._adapter.getMarkersList();
+        markersList.map((marker: MarkerListItem, index: number) => {
+            if (currentValue < marker.end && currentValue > marker.start) {
+                this._adapter.setIsHandleHovering(true);
+                this._adapter.setActiveIndex(index);
+            }
+        });
+    }
+
+    getValueWidth = (marker: MarkerListItem, value: number) => {
+        const { start, end } = marker;
+        if (value > end) {
+            return 'calc(100% - 2px)';
+        } else if (value < start) {
+            return '0%';
+        } else {
+            return `${(value - start) / (end - start) * 100}%`;
+        }
+    }
+
+    // Get the width of the video being played
+    getPlayedWidth = (marker: MarkerListItem) => {
+        const { value: currentValue } = this.getProps();
+        return this.getValueWidth(marker, currentValue);
+    }
+
+    getLoadedWidth = (marker: MarkerListItem) => {
+        const { bufferedValue } = this.getProps();
+        return this.getValueWidth(marker, bufferedValue);
+    }
+
+
+}   

+ 75 - 0
packages/semi-foundation/videoPlayer/variables.scss

@@ -0,0 +1,75 @@
+// Color
+$color-videoPlayer_theme_dark-text: var(--semi-color-bg-0); // dark theme 字体色
+$color-videoPlayer_theme_dark-bg: rgba(var(--semi-grey-8), 1); // dark theme 背景色
+$color-videoPlayer_theme_light-text: rgba(var(--semi-grey-8), 1); // light theme 字体色
+$color-videoPlayer_theme_light-bg: var(--semi-color-disabled-bg); // light theme 背景色
+$color-videoPlayer_pause-bg: var(--semi-color-text-1); // 暂停按钮颜色
+$color-videoPlayer_notification-bg: var(--semi-color-overlay-bg); // notification 背景色
+$color-videoPlayer_notification-text: var(--semi-color-default); // notification 字体色
+$color-videoPlayer_controls-bg: rgba(28, 31, 35, 0.8); // 控制栏背景色
+$color-videoPlayer_controls_item-bg: var(--semi-color-overlay-bg); // 控制栏 item 背景色
+$color-videoPlayer_controls-text: #fff; // 控制栏文字色
+$color-videoPlayer_controls_item_popup-bg-default: var(--semi-color-overlay-bg); // 控制栏弹层背景色
+$color-videoPlayer_controls_item_popup-bg-hover: rgba(67, 68, 74, 1); // 控制栏弹层背景色 - hover
+$color-videoPlayer_controls_popup_item-text-default: rgba(#fff, 0.7); // 控制栏文字色
+$color-videoPlayer_controls_popup_item-text-active: var(--semi-color-primary); // 控制栏弹层文字色 - 选中
+$color-videoPlayer_progress_bar-bg-played: var(--semi-color-primary); // 进度条已播放部分背景色
+$color-videoPlayer_progress_bar-bg-loaded: rgba(var(--semi-grey-3), 1); // 进度条已加载部分背景色
+$color-videoPlayer_progress_bar-bg-unplayed: rgba(var(--semi-grey-5), 1); // 进度条未播放部分背景色
+$color-videoPlayer_progress_bar_handle-bg: #fff; // handle 背景色
+$color-videoPlayer_progress_bar_handle-border: var(--semi-color-primary); // handle 边框色
+$color-videoPlayer_progress_bar_handle-shadow: var(--semi-color-shadow); // handle 阴影色
+
+// Width/Height
+$height-videoPlayer_progress_bar_hotSpot-default: 20px; // 进度条热区 default 高度
+$height-videoPlayer_progress_bar-default: 4px; // 进度条 default 高度
+$height-videoPlayer_progress_bar-hover: 10px; // 进度条 hover 高度
+$height-videoPlayer_progress_bar_handle: 16px; // 进度条 handle 高度
+$height-videoPlayer_controls_menu-default: 56px; // 控制栏 default 高度
+$height-videoPlayer_controls_volume-default: 160px; // 控制栏音量 default 高度
+$height-videoPlayer_controls_popup-default: 24px; // 控制栏弹层类元素 default 高度
+$height-videoPlayer_controls_popup_item-default: 32px; // 控制栏弹层类元素 item default 高度
+$width-videoPlayer_controls_volume-default: 40px; // 控制栏音量 default 宽度
+$width-videoPlayer_controls_popup_item-default: 50px; // 控制栏弹层类元素 default 宽度
+$width-videoPlayer_controls_popup-default: 48px; // 控制栏弹层 default 宽度
+
+// Spacing
+$spacing-videoPlayer-progress_bar_chapter-marginRight: 2px; // 进度条分节间距
+$spacing-videoPlayer_notification-bottom: 22px; // notification 距离菜单栏距离
+$spacing-videoPlayer_notification-left: 8px; // notification 距离菜单栏左侧距离
+$spacing-videoPlayer_notification_text-paddingY: 8px; // notification 文字垂直方向 padding
+$spacing-videoPlayer_notification_text-paddingX: 12px; // notification 文字水平方向 padding
+$spacing-videoPlayer-controls-padding: $spacing-base-tight $spacing-base; // 控制栏 padding
+$spacing-videoPlayer-controls_item-gap: $spacing-tight; // 控制栏 item 间距
+$spacing-videoPlayer-controls_time-paddingX: 8px; // 控制栏时间 paddingX
+$spacing-videoPlayer-controls_volume_title-paddingX: 10px; // 控制栏音量标题文字 paddingX
+$spacing-videoPlayer-controls_volume_title-paddingY: 0px; // 控制栏音量标题文字 paddingX
+$spacing-videoPlayer-controls_volume_popup-paddingY: 0px; // 控制栏音量弹层 paddingX
+$spacing-videoPlayer-controls_volume_popup-paddingX: $spacing-extra-tight; // 控制栏音量弹层 paddingX
+$spacing-videoPlayer-controls_popup-paddingX: $spacing-tight; // 控制栏弹层元素 paddingX
+$spacing-videoPlayer-controls_popup-paddingY: 0px; // 控制栏弹层元素 paddingY
+$spacing-videoPlayer-progress_bar_wrapper-marginX: $spacing-tight; // 进度条 wrapper marginX
+$spacing-videoPlayer-progress_bar_wrapper-marginY: 0px; // 进度条 wrapper marginY
+$spacing-videoPlayer-progress_bar_tooltip-top: 6px; // 进度条 Tooltip top 值
+$spacing-videoPlayer_progress_bar_handle-top: 15px; // 进度条 handle top 值
+$spacing-videoPlayer_error_svg-marginBottom: 12px; // error svg marginBottom
+
+// Radius
+$radius-videoPlayer_notification: 3px; // notification 圆角
+$radius-videoPlayer_progress_bar_handle: 50%; // 进度条 handle 圆角
+$radius-videoPlayer_progress_bar: 999px; // 进度条圆角
+$radius-videoPlayer_controls_item: 3px; // 控制栏 item 圆角
+$radius-videoPlayer_controls_popup: 4px; // 控制栏弹层圆角
+
+// Font
+$font-videoPlayer_notification-fontSize: $font-size-regular; // notification 字体大小
+$font-videoPlayer_controls_item-fontSize: $font-size-small; // 控制栏 item 字体大小
+$font-videoPlayer_controls_time_text-fontSize: $font-size-regular; // 控制栏时间字体大小
+$font-videoPlayer_error-fontSize: $font-size-regular; // error 字体大小
+$font-videoPlayer_notification-lineHeight: 20px; // notification 行高
+$font-videoPlayer_controls_popup_item-lineHeight: 16px; // 控制栏 item 行高
+$font-videoPlayer_controls_popup_item-fontWeight: 600; // 控制栏 item 字体粗细
+$font-videoPlayer_error-fontWeight: 600; // error 字体粗细
+
+
+

+ 323 - 0
packages/semi-foundation/videoPlayer/videoPlayer.scss

@@ -0,0 +1,323 @@
+@import './variables.scss';
+@import './animation.scss';
+
+$module: #{$prefix}-videoPlayer;
+
+.#{$module} {
+    position: relative;
+    display: inline-block;
+    width: 100%;
+    height: 100%;
+
+    // Hide native controls
+    ::-webkit-media-controls {
+        display: none;
+    }
+    
+    &-wrapper {
+        width: 100%;
+        height: 100%;
+        position: relative;
+        line-height: 0;
+
+        video {
+            width: 100%;
+            height: 100%;
+            object-fit: contain;
+        }
+
+        &-dark {
+            background-color: $color-videoPlayer_theme_dark-bg;
+            color: $color-videoPlayer_theme_dark-text;
+        }
+
+        &-light {
+            background-color: $color-videoPlayer_theme_light-bg;
+            color: $color-videoPlayer_theme_light-text;
+        }
+    
+    }
+
+    &-pause {
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        position: absolute;
+        @include all-center;
+        pointer-events: none;
+
+        svg {
+            font-size: 88px;
+            color: $color-videoPlayer_pause-bg;
+            pointer-events: none;
+        }
+    }
+
+    &-error {
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        position: absolute;
+        @include all-center;
+        flex-direction: column;
+        font-weight: $font-videoPlayer_error-fontWeight;
+        font-size: $font-videoPlayer_error-fontSize;
+
+        &-svg {
+            margin-bottom: $spacing-videoPlayer_error_svg-marginBottom;
+        }
+
+        &-dark {
+            background-color: $color-videoPlayer_theme_dark-bg;
+            color: $color-videoPlayer_theme_dark-text;
+
+            path {
+                fill: $color-videoPlayer_theme_dark-text;
+            }
+            
+        }
+
+        &-light {
+            background-color: $color-videoPlayer_theme_light-bg;
+            color: $color-videoPlayer_theme_light-text;
+
+            path {
+                fill: $color-videoPlayer_theme_light-text;
+            }
+        }
+    }
+
+    &-poster {
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        pointer-events: none;
+
+        &-hide {
+            opacity: 0;
+        }
+    }
+
+    &-notification {
+        position: absolute;
+        bottom: $height-videoPlayer_controls_menu-default + $spacing-videoPlayer_notification-bottom;
+        left: $spacing-videoPlayer_notification-left;
+        text-align: center;
+        background-color: $color-videoPlayer_notification-bg;
+        color: $color-videoPlayer_notification-text;
+        padding: $spacing-videoPlayer_notification_text-paddingY $spacing-videoPlayer_notification_text-paddingX;
+        line-height: $font-videoPlayer_notification-lineHeight;
+        border-radius: $radius-videoPlayer_notification;
+        font-size: $font-videoPlayer_notification-fontSize;
+    }
+
+    &-mirror {
+        video {
+            transform: rotateX(0deg) rotateY(180deg);
+        }
+    }
+
+    &-controls {
+        position: absolute;
+        bottom: 0;
+        left: 0;
+        right: 0;
+        height: fit-content;
+        opacity: 1;
+        transition: opacity $animation_duration-videoPlayer_controls-show ease-in-out;
+        z-index: 1;
+
+        &-hide {
+            opacity: 0;
+        }
+
+        &-menu {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            box-sizing: border-box;
+            height: $height-videoPlayer_controls_menu-default;
+            background-color: $color-videoPlayer_controls-bg;
+            padding: $spacing-videoPlayer-controls-padding;
+
+            &-left,
+            &-right {
+                display: flex;
+                align-items: center;
+            }
+
+            &-item {
+                margin-right: $spacing-videoPlayer-controls_item-gap;
+            }
+
+            &-button {
+                svg {
+                    color: $color-videoPlayer_controls-text;
+                }
+            }
+        }
+
+        &-time {
+            color: $color-videoPlayer_controls-text;
+            font-size: $font-videoPlayer_controls_time_text-fontSize;
+            margin-right: $spacing-videoPlayer-controls_item-gap;
+            padding: 0 $spacing-videoPlayer-controls_time-paddingX;
+            font-variant-numeric: tabular-nums; 
+        }
+
+        &-popover {
+            background-color: transparent;
+        }
+
+        &-volume {
+            box-sizing: border-box;
+            width: $width-videoPlayer_controls_volume-default;
+            height: $height-videoPlayer_controls_volume-default;
+            background-color: $color-videoPlayer_controls_item-bg;
+            color: $color-videoPlayer_controls_popup_item-text-default;
+            border-radius: $radius-videoPlayer_controls_popup;
+            padding: $spacing-videoPlayer-controls_volume_popup-paddingY $spacing-videoPlayer-controls_volume_popup-paddingX;
+            line-height: $font-videoPlayer_controls_popup_item-lineHeight;
+            font-size: $font-videoPlayer_controls_item-fontSize;
+            user-select: none;
+
+            &-title {
+                padding: $spacing-videoPlayer-controls_volume_title-paddingX $spacing-videoPlayer-controls_volume_title-paddingY;
+                text-align: center;
+            }
+        }
+
+        &-popup {
+            @include all-center;
+            width: $width-videoPlayer_controls_popup_item-default;
+            height: $height-videoPlayer_controls_popup-default;
+            background-color: $color-videoPlayer_controls_item_popup-bg-default;
+            color: $color-videoPlayer_controls-text;
+            font-weight: $font-videoPlayer_controls_popup_item-fontWeight;
+            font-size: $font-videoPlayer_controls_item-fontSize;
+            line-height: $font-videoPlayer_controls_popup_item-lineHeight;
+            border-radius: $radius-videoPlayer_controls_item;
+            cursor: pointer;
+
+            &-menu {
+                width: $width-videoPlayer_controls_popup-default;
+                background-color: $color-videoPlayer_controls_item_popup-bg-default;
+                border-radius: $radius-videoPlayer_controls_popup;
+
+                &-item {
+                    @include all-center;
+                    padding: $spacing-videoPlayer-controls_popup-paddingY $spacing-videoPlayer-controls_popup-paddingX;
+                    height: $height-videoPlayer_controls_popup_item-default;
+                    color: $color-videoPlayer_controls-text;
+                    font-size: $font-videoPlayer_controls_item-fontSize;
+
+                    &:hover {
+                        background-color: $color-videoPlayer_controls_item_popup-bg-hover !important;
+                    }
+                }
+
+                .#{$prefix}-dropdown-item-active {
+                    color: $color-videoPlayer_controls_popup_item-text-active;
+                    font-weight: $font-videoPlayer_controls_popup_item-fontWeight;
+                    cursor: pointer;
+                }
+            }
+        }
+
+    }
+
+    &-progress {
+        position: relative;
+        height: $height-videoPlayer_progress_bar_hotSpot-default;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        cursor: pointer;
+        margin: $spacing-videoPlayer-progress_bar_wrapper-marginY $spacing-videoPlayer-progress_bar_wrapper-marginX;
+
+        &-markers {
+            position: absolute;
+            width: 100%;
+            height: 100%;
+        }
+
+        &-tooltip {
+            top: $spacing-videoPlayer-progress_bar_tooltip-top;
+
+            &-content {
+                @include all-center;
+            }
+        }
+
+        &-slider {
+            position: absolute;
+            width: 100%;
+            height: 100%;
+            left: 0;
+            bottom: 0;
+            border-radius: $radius-videoPlayer_progress_bar;
+
+            &-active, &:hover {
+                .#{$module}-progress-slider-list, 
+                .#{$module}-progress-slider-played,
+                .#{$module}-progress-slider-buffered {
+                    height: $height-videoPlayer_progress_bar-hover;
+                    transition: transform $animation_duration-videoPlayer_slider-in;
+                    transition-timing-function: $animation_function-videoPlayer_slider-in;
+                }
+            }
+
+            &-list {
+                position: absolute;
+                bottom: 0;
+                left: 0;
+                background-color: $color-videoPlayer_progress_bar-bg-unplayed;
+                height: $height-videoPlayer_progress_bar-default;
+                width: calc(100% - $spacing-videoPlayer-progress_bar_chapter-marginRight);
+                margin-right: $spacing-videoPlayer-progress_bar_chapter-marginRight;
+                border-radius: $radius-videoPlayer_progress_bar;
+                transition: transform $animation_duration-videoPlayer_slider-in;
+                transition-timing-function: $animation_function-videoPlayer_slider-out;
+            }
+
+            &-played {
+                position: absolute;
+                bottom: 0;
+                left: 0;
+                background-color: $color-videoPlayer_progress_bar-bg-played;
+                height: $height-videoPlayer_progress_bar-default;
+                border-radius: $radius-videoPlayer_progress_bar;
+                transition: transform $animation_duration-videoPlayer_slider-in;
+                transition-timing-function: $animation_function-videoPlayer_slider-out;
+            }
+
+            &-buffered {
+                position: absolute;
+                bottom: 0;
+                left: 0;
+                background-color: $color-videoPlayer_progress_bar-bg-loaded;
+                height: $height-videoPlayer_progress_bar-default;
+                border-radius: $radius-videoPlayer_progress_bar;
+                transition: transform $animation_duration-videoPlayer_slider-in;
+                transition-timing-function: $animation_function-videoPlayer_slider-out;
+            }
+        }
+
+        &-handle {
+            box-sizing: border-box;
+            position: absolute;
+            width: $height-videoPlayer_progress_bar_handle;
+            height: $height-videoPlayer_progress_bar_handle;
+            background-color: $color-videoPlayer_progress_bar_handle-bg;
+            border: 1px solid $color-videoPlayer_progress_bar_handle-border;
+            box-shadow: 0px 0px 4px 0px $color-videoPlayer_progress_bar_handle-shadow;
+            border-radius: $radius-videoPlayer_progress_bar_handle;
+            top: $spacing-videoPlayer_progress_bar_handle-top;
+        }
+        
+    }
+} 

+ 1 - 1
packages/semi-icons-lab/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-icons-lab",
-  "version": "2.79.0",
+  "version": "2.80.0-beta.0",
   "description": "semi icons lab",
   "keywords": [
     "semi",

+ 1 - 1
packages/semi-icons/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-icons",
-    "version": "2.79.0",
+    "version": "2.80.0-beta.0",
     "description": "semi icons",
     "keywords": [
         "semi",

+ 24 - 0
packages/semi-icons/src/icons/IconMiniPlayer.tsx

@@ -0,0 +1,24 @@
+import * as React from 'react';
+import { convertIcon } from '../components/Icon';
+function SvgComponent(props: React.SVGProps<SVGSVGElement>) {
+    return (
+        <svg 
+            viewBox="0 0 24 24"
+            fill="none"
+            xmlns="http://www.w3.org/2000/svg"
+            width="1em"
+            height="1em"
+            focusable={false}
+            aria-hidden={true}
+            {...props}
+        >
+            <path d="M15.501 22.0005C14.6725 22.0005 14.001 21.3289 14.001 20.5005V15.5005C14.001 14.6721 14.6725 14.0005 15.501 14.0005H20.501C21.3294 14.0005 22.001 14.6721 22.001 15.5005V20.5005C22.001 21.3289 21.3294 22.0005 20.501 22.0005L15.501 22.0005Z" fill="currentColor"/>
+            <path d="M5.50098 22.0005C3.56798 22.0005 2.00098 20.4335 2.00098 18.5005V15.5005V5.50049C2.00098 3.56749 3.56798 2.00049 5.50098 2.00049H15.501H18.501C20.434 2.00049 22.001 3.56749 22.001 5.50049V9.75038C22.001 10.5788 21.3294 11.2504 20.501 11.2504C19.6726 11.2504 19.001 10.5788 19.001 9.75038V5.50049C19.001 5.22435 18.7771 5.00049 18.501 5.00049H15.501H5.50098C5.22483 5.00049 5.00098 5.22435 5.00098 5.50049V15.5005V18.5005C5.00098 18.7766 5.22483 19.0005 5.50098 19.0005H9.74999C10.5784 19.0005 11.25 19.6721 11.25 20.5005C11.25 21.3289 10.5784 22.0005 9.74999 22.0005H5.50098Z" fill="currentColor"/>
+            <path fillRule="evenodd" clipRule="evenodd" d="M7.52045 7.18658C7.12993 7.57711 7.12994 8.21026 7.52046 8.60078L10.4702 11.5505L9.64175 11.5505C9.08946 11.5505 8.64175 11.9982 8.64175 12.5505C8.64175 13.1028 9.08946 13.5505 9.64175 13.5505L12.8844 13.5505C13.4367 13.5505 13.8844 13.1028 13.8844 12.5505L13.8844 9.30788C13.8844 8.75559 13.4367 8.30788 12.8844 8.30788C12.3321 8.30788 11.8844 8.7556 11.8844 9.30788L11.8844 10.1363L8.93467 7.18657C8.54414 6.79604 7.91097 6.79605 7.52045 7.18658V7.18658Z" fill="currentColor"/>
+        </svg>
+    );
+}
+const IconComponent = convertIcon(SvgComponent, 'mini_player');
+export default IconComponent;
+
+

+ 1 - 0
packages/semi-icons/src/icons/index.ts

@@ -264,6 +264,7 @@ export { default as IconMenu } from './IconMenu';
 export { default as IconMicrophone } from './IconMicrophone';
 export { default as IconMicrophoneOff } from './IconMicrophoneOff';
 export { default as IconMinimize } from './IconMinimize';
+export { default as IconMiniPlayer } from './IconMiniPlayer';
 export { default as IconMinus } from './IconMinus';
 export { default as IconMinusCircle } from './IconMinusCircle';
 export { default as IconMinusCircleStroked } from './IconMinusCircleStroked';

+ 1 - 1
packages/semi-illustrations/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-illustrations",
-    "version": "2.79.0",
+    "version": "2.80.0-beta.0",
     "description": "semi illustrations",
     "keywords": [
         "semi",

+ 1 - 1
packages/semi-json-viewer-core/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-json-viewer-core",
-    "version": "2.79.0",
+    "version": "2.80.0-beta.0",
     "description": "",
     "main": "lib/index.js",
     "module": "lib/index.js",

+ 2 - 2
packages/semi-next/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-next",
-    "version": "2.79.0",
+    "version": "2.80.0-beta.0",
     "description": "Plugin that support Semi Design in Next.js",
     "author": "伍浩威 <[email protected]>",
     "homepage": "",
@@ -22,7 +22,7 @@
         "typescript": "^4"
     },
     "dependencies": {
-        "@douyinfe/semi-webpack-plugin": "2.79.0"
+        "@douyinfe/semi-webpack-plugin": "2.80.0-beta.0"
     },
     "gitHead": "eb34a4f25f002bb4cbcfa51f3df93bed868c831a"
 }

+ 1 - 1
packages/semi-rspack/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-rspack-plugin",
-    "version": "2.79.0",
+    "version": "2.80.0-beta.0",
     "description": "",
     "homepage": "",
     "license": "MIT",

+ 1 - 1
packages/semi-scss-compile/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-scss-compile",
-    "version": "2.79.0",
+    "version": "2.80.0-beta.0",
     "description": "compile semi scss to css",
     "author": "[email protected]",
     "license": "MIT",

+ 1 - 1
packages/semi-theme-default/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-theme-default",
-    "version": "2.79.0",
+    "version": "2.80.0-beta.0",
     "description": "semi-theme-default",
     "keywords": [
         "semi-theme",

+ 1 - 0
packages/semi-ui/index.ts

@@ -128,3 +128,4 @@ export { default as DragMove } from './dragMove';
 export { default as Cropper } from './cropper';
 export { default as AudioPlayer } from './audioPlayer';
 export { default as UserGuide } from './userGuide';
+export { default as VideoPlayer } from './videoPlayer';

+ 11 - 0
packages/semi-ui/locale/interface.ts

@@ -194,5 +194,16 @@ export interface Locale {
         search: string;
         replace: string;
         replaceAll: string
+    };
+    VideoPlayer: {
+        rateChange: string;
+        qualityChange: string;
+        routeChange: string;
+        mirror: string;
+        cancelMirror: string;
+        loading: string;
+        stall: string;
+        noResource: string;
+        videoError: string
     }
 }

+ 11 - 0
packages/semi-ui/locale/source/ar.ts

@@ -195,6 +195,17 @@ const local: Locale = {
         replace: 'استبدل',
         replaceAll: 'استبدل الكل',
     },
+    VideoPlayer: {
+        rateChange: 'تحويل السرعة إلى ${rate}',
+        qualityChange: 'تحويل الجودة إلى ${quality}',
+        routeChange: 'تحويل المسار إلى ${route}',
+        mirror: 'المرآة',
+        cancelMirror: 'إلغاء المرآة',
+        loading: 'جار التحميل...',
+        stall: 'فشل التحميل',
+        noResource: 'لا يوجد مورد',
+        videoError: 'خطأ في تحميل الفيديو'
+    }
 };
 
 // [i18n-Arabic]

+ 11 - 0
packages/semi-ui/locale/source/de.ts

@@ -195,6 +195,17 @@ const local: Locale = {
         replace: 'Ersetzen',
         replaceAll: 'Alle ersetzen',
     },
+    VideoPlayer: {
+        rateChange: 'Geschwindigkeit auf ${rate} wechseln',
+        qualityChange: 'Qualität auf ${quality} wechseln',
+        routeChange: 'Route auf ${route} wechseln',
+        mirror: 'Spiegel',
+        cancelMirror: 'Spiegelung aufheben',
+        loading: 'Wird geladen...',
+        stall: 'Laden fehlgeschlagen',
+        noResource: 'Keine Ressource',
+        videoError: 'Video-Ladefehler'
+    }
 };
 
 // [i18n-German]

+ 11 - 0
packages/semi-ui/locale/source/en_GB.ts

@@ -195,6 +195,17 @@ const local: Locale = {
         replace: 'Replace',
         replaceAll: 'Replace All',
     },
+    VideoPlayer: {
+        rateChange: 'Switch rate to ${rate}',
+        qualityChange: 'Switch quality to ${quality}',
+        routeChange: 'Switch route to ${route}',
+        mirror: 'Mirror',
+        cancelMirror: 'Cancel mirror',
+        loading: 'Loading...',
+        stall: 'Loading failed',
+        noResource: 'No resource',
+        videoError: 'Video load error'
+    }
 };
 
 // [i18n-English(GB)]

+ 11 - 0
packages/semi-ui/locale/source/en_US.ts

@@ -195,6 +195,17 @@ const local: Locale = {
         replace: 'Replace',
         replaceAll: 'Replace All',
     },
+    VideoPlayer: {
+        rateChange: 'Switch rate to ${rate}',
+        qualityChange: 'Switch quality to ${quality}',
+        routeChange: 'Switch route to ${route}',
+        mirror: 'Mirror',
+        cancelMirror: 'Cancel mirror',
+        loading: 'Loading...',
+        stall: 'Loading failed',
+        noResource: 'No resource',
+        videoError: 'Video load error',
+    }
 };
 
 // [i18n-English(US)]

+ 11 - 0
packages/semi-ui/locale/source/es.ts

@@ -200,6 +200,17 @@ const locale: Locale = {
         replace: 'Reemplazar',
         replaceAll: 'Reemplazar todo',
     },
+    VideoPlayer: {
+        rateChange: 'Cambiar velocidad a ${rate}',
+        qualityChange: 'Cambiar calidad a ${quality}',
+        routeChange: 'Cambiar ruta a ${route}',
+        mirror: 'Espejo',
+        cancelMirror: 'Cancelar espejo',
+        loading: 'Cargando...',
+        stall: 'Carga fallida',
+        noResource: 'Sin recursos',
+        videoError: 'Error al cargar el video'
+    }
 };
 
 export default locale;

+ 11 - 0
packages/semi-ui/locale/source/fr.ts

@@ -195,6 +195,17 @@ const local: Locale = {
         replace: 'Remplacer',
         replaceAll: 'Remplacer tout',
     },
+    VideoPlayer: {
+        rateChange: 'Changer la vitesse à ${rate}',
+        qualityChange: 'Changer la qualité à ${quality}',
+        routeChange: 'Changer la route à ${route}',
+        mirror: 'Miroir',
+        cancelMirror: 'Annuler le miroir',
+        loading: 'Chargement...',
+        stall: 'Chargement échoué',
+        noResource: 'Aucune ressource',
+        videoError: 'Erreur de chargement de la vidéo'
+    }
 };
 
 // [i18n-French]

+ 11 - 0
packages/semi-ui/locale/source/id_ID.ts

@@ -195,6 +195,17 @@ const local: Locale = {
         replace: 'Ganti',
         replaceAll: 'Ganti Semua',
     },
+    VideoPlayer: {
+        rateChange: 'Ubah kecepatan ke ${rate}',
+        qualityChange: 'Ubah kualitas ke ${quality}',
+        routeChange: 'Ubah rute ke ${route}',
+        mirror: 'Cermin',
+        cancelMirror: 'Hapus cermin',
+        loading: 'Memuat...',
+        stall: 'Memuat gagal',
+        noResource: 'Tidak ada sumber',
+        videoError: 'Kesalahan memuat video'
+    }
 };
 
 // [i18n-Indonesia(ID)]

+ 11 - 0
packages/semi-ui/locale/source/it.ts

@@ -195,6 +195,17 @@ const local: Locale = {
         replace: 'Sostituisci',
         replaceAll: 'Sostituisci tutto',
     },
+    VideoPlayer: {
+        rateChange: 'Cambia velocità a ${rate}',
+        qualityChange: 'Cambia qualità a ${quality}',
+        routeChange: 'Cambia route a ${route}',
+        mirror: 'Specchio',
+        cancelMirror: 'Rimuovi specchio',
+        loading: 'Caricamento in corso...',
+        stall: 'Caricamento fallito',
+        noResource: 'Nessuna risorsa',
+        videoError: 'Errore di caricamento video'
+    }
 };
 
 // [i18n-Italian]

+ 11 - 0
packages/semi-ui/locale/source/ja_JP.ts

@@ -196,6 +196,17 @@ const local: Locale = {
         replace: '置換',
         replaceAll: 'すべて置換',
     },
+    VideoPlayer: {
+        rateChange: '速さを${rate}に変更',
+        qualityChange: '品質を${quality}に変更',
+        routeChange: 'ルートを${route}に変更',
+        mirror: '鏡像',
+        cancelMirror: '鏡像を解除',
+        loading: '読み込み中...',
+        stall: '読み込みに失敗しました',
+        noResource: 'リソースなし',
+        videoError: '動画の読み込みエラー'
+    }
 };
 
 // [i18n-Japan]

+ 11 - 0
packages/semi-ui/locale/source/ko_KR.ts

@@ -196,6 +196,17 @@ const local: Locale = {
         replace: '교체',
         replaceAll: '모두 교체',
     },
+    VideoPlayer: {
+        rateChange: '속도를 ${rate}로 변경',
+        qualityChange: '품질을 ${quality}로 변경',
+        routeChange: '경로를 ${route}로 변경',
+        mirror: '거울',
+        cancelMirror: '거울 해제',
+        loading: '로딩 중...',
+        stall: '로딩 실패',
+        noResource: '리소스 없음',
+        videoError: '비디오 로드 오류'
+    }
 };
 
 // [i18n-Korea]

+ 11 - 0
packages/semi-ui/locale/source/ms_MY.ts

@@ -195,6 +195,17 @@ const local: Locale = {
         replace: 'Ganti',
         replaceAll: 'Ganti Semua',
     },
+    VideoPlayer: {
+        rateChange: 'Ubah kecepatan ke ${rate}',
+        qualityChange: 'Ubah kualitas ke ${quality}',
+        routeChange: 'Ubah rute ke ${route}',
+        mirror: 'Cermin',
+        cancelMirror: 'Hapus cermin',
+        loading: 'Memuat...',
+        stall: 'Memuat gagal',
+        noResource: 'Tiada sumber',
+        videoError: 'Ralat memuatkan video'
+    }
 };
 
 // [i18n-Malaysia(MY)]

+ 11 - 0
packages/semi-ui/locale/source/nl_NL.ts

@@ -202,6 +202,17 @@ const local: Locale = {
         replace: 'Vervangen',
         replaceAll: 'Alle vervangen',
     },
+    VideoPlayer: {
+        rateChange: 'Verander snelheid naar ${rate}',
+        qualityChange: 'Verander kwaliteit naar ${quality}',
+        routeChange: 'Verander route naar ${route}',
+        mirror: 'Spiegel',
+        cancelMirror: 'Spiegel opheffen',
+        loading: 'Laden...',
+        stall: 'Laden mislukt',
+        noResource: 'Geen bron',
+        videoError: 'Fout bij laden video'
+    }
 };
 
 export default local;

+ 11 - 0
packages/semi-ui/locale/source/pl_PL.ts

@@ -203,6 +203,17 @@ const local: Locale = {
         replace: 'Zastąp',
         replaceAll: 'Zastąp wszystko',
     },
+    VideoPlayer: {
+        rateChange: 'Zmień prędkość na ${rate}',
+        qualityChange: 'Zmień jakość na ${quality}',
+        routeChange: 'Zmień ścieżkę na ${route}',
+        mirror: 'Lustrzane odbicie',
+        cancelMirror: 'Odwróć lustrzane odbicie',
+        loading: 'Ładowanie...',
+        stall: 'Ładowanie nie powiodło się',
+        noResource: 'Brak zasobu',
+        videoError: 'Błąd ładowania wideo'
+    }
 };
 
 export default local;

+ 11 - 0
packages/semi-ui/locale/source/pt_BR.ts

@@ -203,6 +203,17 @@ const local: Locale = {
         replace: 'Substituir',
         replaceAll: 'Substituir tudo',
     },
+    VideoPlayer: {
+        rateChange: 'Mudar velocidade para ${rate}',
+        qualityChange: 'Mudar qualidade para ${quality}',
+        routeChange: 'Mudar rota para ${route}',
+        mirror: 'Espelho',
+        cancelMirror: 'Remover espelho',
+        loading: 'Carregando...',
+        stall: 'Carregamento falhou',
+        noResource: 'Sem recurso',
+        videoError: 'Erro ao carregar vídeo'
+    }
 };
 
 // 葡萄牙语

+ 11 - 0
packages/semi-ui/locale/source/ro.ts

@@ -195,6 +195,17 @@ const local: Locale = {
         replace: 'Înlocuiește',
         replaceAll: 'Înlocuiește toate',
     },
+    VideoPlayer: {
+        rateChange: 'Schimbați viteza la ${rate}',
+        qualityChange: 'Schimbați calitatea la ${quality}',
+        routeChange: 'Schimbați ruta la ${route}',
+        mirror: 'Mirror',
+        cancelMirror: 'Anulează oglindirea',
+        loading: 'Se încarcă',
+        stall: 'Se încarcă',
+        noResource: 'Nicio resursă',
+        videoError: 'Eroare la încărcarea videoclipului'
+    },
 };
 
 // [i18n-Romanian] 罗马尼亚语

+ 11 - 0
packages/semi-ui/locale/source/ru_RU.ts

@@ -198,6 +198,17 @@ const local: Locale = {
         replace: 'Заменить',
         replaceAll: 'Заменить все',
     },
+    VideoPlayer: {
+        rateChange: 'Изменить скорость на ${rate}',
+        qualityChange: 'Изменить качество на ${quality}',
+        routeChange: 'Изменить маршрут на ${route}',
+        mirror: 'Зеркало',
+        cancelMirror: 'Отменить зеркало',
+        loading: 'Загрузка...',
+        stall: 'Загрузка не удалась',
+        noResource: 'Нет ресурса',
+        videoError: 'Ошибка загрузки видео'
+    }
 };
 
 // [i18n-Russia] 俄罗斯语

+ 11 - 0
packages/semi-ui/locale/source/sv_SE.ts

@@ -200,6 +200,17 @@ const local: Locale = {
         replace: 'Ersätt',
         replaceAll: 'Ersätt alla',
     },
+    VideoPlayer: {
+        rateChange: 'Ändra hastighet till ${rate}',
+        qualityChange: 'Ändra kvalitet till ${quality}',
+        routeChange: 'Ändra väg till ${route}',
+        mirror: 'Spegel',
+        cancelMirror: 'Ta bort spegel',
+        loading: 'Läser in...',
+        stall: 'Läsning misslyckades',
+        noResource: 'Ingen resurs',
+        videoError: 'Fel vid inläsning av video'
+    }
 };
 
 export default local;

+ 11 - 0
packages/semi-ui/locale/source/th_TH.ts

@@ -199,6 +199,17 @@ const local: Locale = {
         replace: 'แทนที่',
         replaceAll: 'แทนที่ทั้งหมด',
     },
+    VideoPlayer: {
+        rateChange: 'เปลี่ยนความเร็วเป็น ${rate}',
+        qualityChange: 'เปลี่ยนคุณภาพเป็น ${quality}',
+        routeChange: 'เปลี่ยนเส้นทางเป็น ${route}',
+        mirror: 'กลับหน้า',
+        cancelMirror: 'ยกเลิกกลับหน้า',
+        loading: 'กำลังโหลด...',
+        stall: 'กำลังโหลดล้มเหลว',
+        noResource: 'ไม่มีทรัพยากร',
+        videoError: 'เกิดข้อผิดพลาดในการโหลดวิดีโอ'
+    }
 };
 
 // [i18n-Thai]

+ 11 - 0
packages/semi-ui/locale/source/tr_TR.ts

@@ -196,6 +196,17 @@ const local: Locale = {
         replace: 'Değiştir',
         replaceAll: 'Tümünü değiştir',
     },
+    VideoPlayer: {
+        rateChange: 'Hızı ${rate}\'e değiştir',
+        qualityChange: 'Kaliteyi ${quality}\'e değiştir',
+        routeChange: 'Rota ${route}\'e değiştir',
+        mirror: 'Ayna',
+        cancelMirror: 'Aynayı kaldır',
+        loading: 'Yükleniyor...',
+        stall: 'Yükleme başarısız',
+        noResource: 'Kaynak yok',
+        videoError: 'Video yükleme hatası'
+    }
 };
 
 // [i18n-Turkish] 

+ 11 - 0
packages/semi-ui/locale/source/vi_VN.ts

@@ -198,6 +198,17 @@ const local: Locale = {
         replace: 'Thay thế',
         replaceAll: 'Thay thế tất cả',
     },
+    VideoPlayer: {
+        rateChange: 'Thay đổi tốc độ thành ${rate}',
+        qualityChange: 'Thay đổi chất lượng thành ${quality}',
+        routeChange: 'Thay đổi tuyến đường thành ${route}',
+        mirror: 'Gương',
+        cancelMirror: 'Hủy gương',
+        loading: 'Đang tải...',
+        stall: 'Tải không thành công',
+        noResource: 'Không có tài nguyên',
+        videoError: 'Lỗi tải video'
+    }
 };
 
 // [i18n-Vietnam] 越南语

+ 11 - 0
packages/semi-ui/locale/source/zh_CN.ts

@@ -196,6 +196,17 @@ const local: Locale = {
         replace: '替换',
         replaceAll: '全部替换',
     },
+    VideoPlayer: {
+        rateChange: '切换速率至 ${rate}',
+        qualityChange: '切换清晰度至${quality}',
+        routeChange: '切换线路至${route}',
+        mirror: '镜像',
+        cancelMirror: '取消镜像',
+        loading: '加载中...',
+        stall: '加载失败',
+        noResource: '暂无资源',
+        videoError: '视频加载错误'
+    }
 };
 
 // 中文

+ 11 - 0
packages/semi-ui/locale/source/zh_TW.ts

@@ -196,6 +196,17 @@ const local: Locale = {
         replace: '替換',
         replaceAll: '全部替換',
     },
+    VideoPlayer: {
+        rateChange: '切換速率至 ${rate}',
+        qualityChange: '切換清晰度至${quality}',
+        routeChange: '切換路徑至${route}',
+        mirror: '鏡像',
+        cancelMirror: '取消鏡像',
+        loading: '加載中...',
+        stall: '加載失敗',
+        noResource: '暫無資源',
+        videoError: '視頻加載錯誤'
+    }
 };
 
 // 中文

+ 7 - 7
packages/semi-ui/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-ui",
-    "version": "2.79.0",
+    "version": "2.80.0-beta.0",
     "description": "A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.",
     "main": "lib/cjs/index.js",
     "module": "lib/es/index.js",
@@ -20,12 +20,12 @@
         "@dnd-kit/core": "^6.0.8",
         "@dnd-kit/sortable": "^7.0.2",
         "@dnd-kit/utilities": "^3.2.1",
-        "@douyinfe/semi-animation": "2.79.0",
-        "@douyinfe/semi-animation-react": "2.79.0",
-        "@douyinfe/semi-foundation": "2.79.0",
-        "@douyinfe/semi-icons": "2.79.0",
-        "@douyinfe/semi-illustrations": "2.79.0",
-        "@douyinfe/semi-theme-default": "2.79.0",
+        "@douyinfe/semi-animation": "2.80.0-beta.0",
+        "@douyinfe/semi-animation-react": "2.80.0-beta.0",
+        "@douyinfe/semi-foundation": "2.80.0-beta.0",
+        "@douyinfe/semi-icons": "2.80.0-beta.0",
+        "@douyinfe/semi-illustrations": "2.80.0-beta.0",
+        "@douyinfe/semi-theme-default": "2.80.0-beta.0",
         "async-validator": "^3.5.0",
         "classnames": "^2.2.6",
         "copy-text-to-clipboard": "^2.1.1",

+ 1 - 1
packages/semi-ui/tsconfig.json

@@ -29,4 +29,4 @@
     },
     "include": ["**/*.tsx", "**/*.ts"],
     "exclude": ["node_modules", "packages/rollup-plugin-semi-svg"]
-}
+}

+ 15 - 0
packages/semi-ui/videoPlayer/ErrorSvg.tsx

@@ -0,0 +1,15 @@
+import React from 'react';
+
+const ErrorSvg = () => {
+    return <svg xmlns="http://www.w3.org/2000/svg" width="41" height="30" viewBox="0 0 41 30" fill="none">
+        <path 
+            fillRule="evenodd" 
+            clipRule="evenodd" 
+            d="M3.875 4.5C3.875 3.67157 4.54657 3 5.375 3H29.375C30.2034 3 30.875 3.67157 30.875 4.5V11.0446C30.875 11.9448 31.9714 12.3866 32.5956 11.7379L36.2647 7.92487C36.5768 7.60054 37.125 7.82146 37.125 8.27156V21.7284C37.125 22.1785 36.5768 22.3995 36.2647 22.0751L32.5956 18.2621C31.9714 17.6134 30.875 18.0552 30.875 18.9554V25.5C30.875 26.3284 30.2034 27 29.375 27H5.375C4.54657 27 3.875 26.3284 3.875 25.5V4.5ZM5.375 0C2.88972 0 0.875 2.01472 0.875 4.5V25.5C0.875 27.9853 2.88972 30 5.375 30H29.375C31.8603 30 33.875 27.9853 33.875 25.5V23.9183L34.103 24.1553C36.2876 26.4256 40.125 24.8792 40.125 21.7284V8.27156C40.125 5.12082 36.2876 3.5744 34.103 5.84475L33.875 6.08167V4.5C33.875 2.01472 31.8603 0 29.375 0H5.375ZM13.875 10.5C13.875 9.67157 13.2034 9 12.375 9C11.5466 9 10.875 9.67157 10.875 10.5V14.5C10.875 15.3284 11.5466 16 12.375 16C13.2034 16 13.875 15.3284 13.875 14.5V10.5ZM23.875 9C24.7034 9 25.375 9.67157 25.375 10.5V14.5C25.375 15.3284 24.7034 16 23.875 16C23.0466 16 22.375 15.3284 22.375 14.5V10.5C22.375 9.67157 23.0466 9 23.875 9ZM12.6231 22.3321C13.3039 21.3108 14.9319 20 18.375 20C20.192 20 21.3252 20.2374 22.1317 20.611C22.9124 20.9727 23.5161 21.5215 24.175 22.4C24.6721 23.0627 25.6123 23.1971 26.275 22.7C26.9377 22.2029 27.0721 21.2627 26.575 20.6C25.7339 19.4785 24.7706 18.5273 23.3928 17.889C22.0407 17.2626 20.424 17 18.375 17C14.0862 17 11.4461 18.6892 10.1269 20.6679C9.6674 21.3572 9.85366 22.2885 10.5429 22.7481C11.2322 23.2076 12.1635 23.0213 12.6231 22.3321Z" 
+            fill="#F9F9F9" 
+            fillOpacity="0.8"
+        />
+    </svg>;
+};
+
+export default ErrorSvg;

+ 271 - 0
packages/semi-ui/videoPlayer/_story/videoPlayer.stories.jsx

@@ -0,0 +1,271 @@
+import React, { useState } from 'react';
+import { VideoPlayer, Select, Typography } from '@douyinfe/semi-ui';
+
+export default {
+    title: 'VideoPlayer',
+};
+
+export const BasicUsage = () => {
+    return (
+        <div>
+            <VideoPlayer 
+                src={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4'}
+                poster={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg'}
+                width={'500px'}
+                height={'280px'} 
+                onPause={() => {
+                    console.log('pause');
+                }}
+                onPlay={() => {
+                    console.log('play');
+                }}
+            />
+        </div>
+    )
+}
+
+// 设置菜单栏功能
+export const ControlList = () => {
+    return (
+        <div>
+            <VideoPlayer 
+                width={'500px'}
+                height={'280px'} 
+                src={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4'}
+                poster={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg'}
+                controlsList={['play', 'time', 'volume', 'playbackRate', 'fullscreen',]}
+            />
+        </div>
+    )
+}
+
+export const Theme = () => {
+    return (
+        <div>
+            <Typography.Title heading={3} style={{ margin: '8px 0' }} >dark theme</Typography.Title>
+            <VideoPlayer 
+                src={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4'}
+                poster={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg'}
+                width={'1000px'}
+                height={'450px'} 
+                controlsList={['play', 'time', 'volume', 'playbackRate', 'fullscreen',]}
+            />
+            <br />
+            <Typography.Title heading={3} style={{ margin: '8px 0' }} >light theme</Typography.Title>
+            <VideoPlayer 
+                src={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4'}
+                poster={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg'}
+                width={'1000px'}
+                height={'450px'} 
+                theme={'light'}
+                controlsList={['play', 'time', 'volume', 'playbackRate', 'fullscreen',]}
+            />
+        </div>
+    )
+}
+
+export const SetSeekTime = () => {
+    const [seekTime, setSeekTime] = useState(5);
+    return (
+        <div>
+            <span style={{ marginBottom: 10 }}>请选择快进快退时间</span>
+            <Select
+                value={seekTime}
+                style={{ width: 100, marginLeft: 10 }}
+                onChange={(value) => setSeekTime(value)}
+                optionList={[
+                    { label: '5s', value: 5 },
+                    { label: '10s', value: 10 },
+                    { label: '15s', value: 15 },
+                ]}
+                placeholder='请选择快进快退时间'
+            />
+            <br />
+            <VideoPlayer 
+                width={'500px'}
+                height={'280px'} 
+                src={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4'}
+                poster={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg'}
+                seekTime={seekTime}
+                style={{ marginTop: 10 }}
+            />
+        </div>
+    )
+}
+
+export const playbackRateList = () => {
+    return (
+        <div>
+            <VideoPlayer 
+                width={'500px'}
+                height={'280px'} 
+                playbackRateList={[
+                    { label: '0.5x', value: 0.5 },
+                    { label: '1.0x', value: 1 },
+                    { label: '1.5x', value: 1.5 },
+                    { label: '2.0x', value: 2 },
+                ]}
+                src={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4'}
+                poster={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg'}
+            />
+        </div>
+    )
+}
+
+// 音量设置
+export const Volume = () => {
+    const src = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4";
+    const poster = "https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg";
+    return (
+        <VideoPlayer 
+            width={'500px'}
+            height={'280px'} 
+            src={src}
+            poster={poster}
+            muted={true}
+            volume={0}
+        />
+    );
+}
+
+export const NoResource = () => {
+    return (
+        <div>
+            <Typography.Title heading={3} style={{ margin: '8px 0' }} >dark theme</Typography.Title>
+            <VideoPlayer 
+                width={'500px'}
+                height={'280px'} 
+                src={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-videoss.mp4'}
+                poster={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg'}
+            />
+            <br />
+            <Typography.Title heading={3} style={{ margin: '8px 0' }} >light theme</Typography.Title>
+            <VideoPlayer 
+                src={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-videoss.mp4'}
+                poster={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg'}
+                theme={'light'}
+                width={'500px'}
+                height={'280px'} 
+            />
+        </div>
+    )
+}
+
+
+// 分章节
+export const Chapter = () => {
+    return (
+        <div>
+            <VideoPlayer 
+                width={'800px'}
+                height={'450px'} 
+                src={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4'}
+                poster={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg'}
+                markers={[
+                    {
+                        start: 0,
+                        title: '片头'
+                    },
+                    {
+                        start: 4,
+                        title: '功能介绍'
+                    },
+                    {
+                        start: 38,
+                        title: 'Figma Plugin'
+                    },
+                    {
+                        start: 51,
+                        title: '片尾'
+                    }
+                ]}
+            />
+        </div>
+    )
+}
+
+// 清晰度和线路调整
+export const QualityAndLine = () => {
+    const [quality, setQuality] = useState('1080p');
+    const [route, setRoute] = useState('线路1');
+    const [src, setSrc] = useState('https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4');
+
+    const playList = [
+        {
+            src: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4',
+            quality: '1080p',
+            route: '线路1',
+        },
+        {
+            src: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/video/vchart-show-video-480p.mp4',
+            quality: '480p',
+            route: '线路1',
+        },
+        {
+            src: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4',
+            quality: '1080p',
+            route: '线路2',
+        },
+        {
+            src: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/video/vchart-show-video-480p.mp4',
+            quality: '480p',
+            route: '线路2',
+        }
+    ]
+
+    const updateVideoSource = (quality, route) => {
+        const source = playList.find((item) => item.quality === quality && item.route === route);
+        console.log('updateVideoSource', quality, route, source);
+        setSrc(source.src);
+    }
+
+    return (
+        <div>
+            <VideoPlayer 
+                src={src}
+                width={'800px'}
+                height={'450px'} 
+                poster={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg'}
+                defaultQuality={'1080p'}
+                defaultRoute={'线路1'}
+                qualityList={[
+                    { label: '1080p', value: '1080p' },
+                    { label: '480p', value: '480p' },
+                ]}
+                routeList={[
+                    { label: '线路1', value: '线路1' },
+                    { label: '线路2', value: '线路2' },
+                ]}
+                onQualityChange={(quality) => {
+                    console.log('quality change', quality);
+                    updateVideoSource(quality, route);
+                    setQuality(quality);
+                }}
+                onRouteChange={(route) => {
+                    console.log('route change', route);
+                    updateVideoSource(quality, route);
+                    setRoute(route);
+                }}
+            />
+        </div>
+    )
+}
+
+export const ScrollDemo = () => {
+    return (
+        <div>
+            滑动到底部全屏后,取消全屏,check 页面滚动是否符合预期
+            <div style={{ marginTop: 1000, overflow: 'auto' }}>
+                <VideoPlayer 
+                    src={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/vchart/landingPage/vchart-show-video.mp4'}
+                    poster={'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/poster2.jpeg'}
+                    theme={'light'}
+                    width={500}
+                    height={280}
+                />
+            </div>
+        </div>
+    )
+}
+
+

+ 519 - 0
packages/semi-ui/videoPlayer/index.tsx

@@ -0,0 +1,519 @@
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+import React from 'react';
+import cls from 'classnames';
+import BaseComponent from '../_base/baseComponent';
+import { cssClasses, DEFAULT_PLAYBACK_RATE, numbers, strings } from '@douyinfe/semi-foundation/videoPlayer/constants';
+import VideoPlayerFoundation, { VideoPlayerAdapter } from '@douyinfe/semi-foundation/videoPlayer/foundation';
+import '@douyinfe/semi-foundation/videoPlayer/videoPlayer.scss';
+import { IconPlay, IconPause, IconVolume1, IconVolume2, IconRestart, IconFlipHorizontal, IconMinimize, IconMaximize, IconMute, IconPlayCircle, IconMiniPlayer } from '@douyinfe/semi-icons';
+import Button from '../button';
+import Popover from '../popover';
+import AudioSlider from '../audioPlayer/audioSlider';
+import Dropdown from '../dropdown';
+import VideoProgress from './videoProgress';
+import { formatTime } from './utils';
+import isNullOrUndefined from '@douyinfe/semi-foundation/utils/isNullOrUndefined';
+import LocaleConsumer from '../locale/localeConsumer';
+import { Locale } from '../locale/interface';
+import ErrorSVG from './ErrorSvg';
+import { Marker } from '@douyinfe/semi-foundation/videoPlayer/progressFoundation';
+
+const prefixCls = cssClasses.PREFIX;
+
+export interface VideoPlayerProps {
+    autoPlay: boolean;
+    captionsSrc?: string;
+    className?: string;
+    clickToPlay: boolean;
+    controlsList?: Array<string>;
+    crossOrigin?: React.MediaHTMLAttributes<HTMLVideoElement>['crossOrigin'];
+    defaultPlaybackRate: number;
+    defaultQuality?: string;
+    defaultRoute?: string;
+    height?: number | string;
+    loop?: boolean;
+    markers?: Marker[];
+    muted: boolean;
+    onPause?: () => void;
+    onPlay?: () => void;
+    onQualityChange?: (quality: string) => void;
+    onRateChange?: (rate: number) => void;
+    onRouteChange?: (route: string) => void;
+    onVolumeChange?: (volume: number) => void;
+    playbackRateList: { label: string; value: number }[];
+    poster?: string;
+    // todo: 预览缩略图
+    // previewThumbnails?: boolean | Record<string, unknown>;
+    qualityList?: Array<{ label: string; value: string }>;
+    routeList?: Array<{ label: string; value: string }>;
+    seekTime?: number;
+    src?: string;
+    style?: React.CSSProperties;
+    theme: string;
+    volume: number;
+    width?: number | string
+}
+
+export interface VideoPlayerState {
+    bufferedValue: number;
+    currentQuality: string;
+    currentRoute: string;
+    currentTime: number;
+    isError: boolean;
+    isMirror: boolean;
+    isPlaying: boolean;
+    muted: boolean;
+    notificationContent: string;
+    playbackRate: number;
+    playbackRateList: { label: string; value: number }[];
+    showNotification: boolean;
+    showControls: boolean;
+    src: string;
+    totalTime: number;
+    volume: number
+}
+
+class VideoPlayer extends BaseComponent<VideoPlayerProps, VideoPlayerState> {
+    static defaultProps: VideoPlayerProps = {
+        autoPlay: false,
+        clickToPlay: true,
+        defaultPlaybackRate: numbers.DEFAULT_PLAYBACK_RATE,
+        controlsList: [strings.PLAY, strings.NEXT, strings.TIME, strings.VOLUME, strings.PLAYBACK_RATE, strings.QUALITY, strings.ROUTE, strings.MIRROR, strings.FULLSCREEN, strings.PICTURE_IN_PICTURE],
+        loop: false,
+        muted: false,
+        playbackRateList: DEFAULT_PLAYBACK_RATE,
+        seekTime: numbers.DEFAULT_SEEK_TIME,
+        theme: strings.DARK,
+        volume: numbers.DEFAULT_VOLUME,
+    };
+
+    private videoRef: React.RefObject<HTMLVideoElement>;
+    private videoWrapperRef: React.RefObject<HTMLDivElement>;
+    foundation: VideoPlayerFoundation;
+
+    constructor(props: VideoPlayerProps) {
+        super(props);
+        this.state = {
+            bufferedValue: 0,
+            currentQuality: props.defaultQuality || '',
+            currentRoute: props.defaultRoute || '',
+            currentTime: 0,
+            isError: false,
+            isMirror: false,
+            isPlaying: false,
+            muted: props.muted,
+            notificationContent: '',
+            playbackRate: props.defaultPlaybackRate || 1,
+            playbackRateList: props.playbackRateList,
+            showNotification: false,
+            showControls: true,
+            src: props.src || '',
+            totalTime: 0,
+            volume: props.muted ? 0 : props.volume,
+        };
+        this.videoRef = React.createRef();
+        this.videoWrapperRef = React.createRef();
+        this.foundation = new VideoPlayerFoundation(this.adapter);
+    }
+
+    get adapter(): VideoPlayerAdapter<VideoPlayerProps, VideoPlayerState> {
+        return {
+            ...super.adapter,
+            getVideo: () => this.videoRef.current,
+            getVideoWrapper: () => this.videoWrapperRef.current,
+            notifyPause: () => this.props.onPause?.(),
+            notifyPlay: () => this.props.onPlay?.(),
+            notifyQualityChange: (quality: string) => this.props.onQualityChange?.(quality),
+            notifyRateChange: (rate: number) => this.props.onRateChange?.(rate),
+            notifyRouteChange: (route: string) => this.props.onRouteChange?.(route),
+            notifyVolumeChange: (volume: number) => this.props.onVolumeChange?.(volume),
+            setBufferedValue: (bufferedValue: number) => this.setState({ bufferedValue }),
+            setCurrentTime: (currentTime: number) => this.setState({ currentTime }),
+            setIsError: (isError: boolean) => this.setState({ isError }),
+            setIsMirror: (isMirror: boolean) => this.setState({ isMirror }),
+            setIsPlaying: (isPlaying: boolean) => this.setState({ isPlaying }),
+            setMuted: (muted: boolean) => this.setState({ muted }),
+            setNotificationContent: (content: string) => this.setState({ notificationContent: content }),
+            setPlaybackRate: (rate: number) => this.setState({ playbackRate: rate }),
+            setQuality: (quality: string) => this.setState({ currentQuality: quality }),
+            setRoute: (route: string) => this.setState({ currentRoute: route }),
+            setShowControls: (showControls: boolean) => this.setState({ showControls }),
+            setShowNotification: (showNotification: boolean) => this.setState({ showNotification: showNotification }),
+            setTotalTime: (totalTime: number) => this.setState({ totalTime }),
+            setVolume: (volume: number) => this.setState({ volume }),
+        };
+    }
+
+    static getDerivedStateFromProps(props: VideoPlayerProps, state: VideoPlayerState): Partial<VideoPlayerState> {
+        const states: Partial<VideoPlayerState> = {};
+        if (!isNullOrUndefined(props.src) && props.src !== state.src) {
+            states.src = props.src;
+        }
+        return states;
+    }
+
+    componentDidMount() {
+        this.foundation.init();
+    }
+
+    componentWillUnmount() {
+        this.foundation.destroy();
+    }
+
+    handleMouseEnterWrapper = () => {
+        this.foundation.handleMouseEnterWrapper();
+    }
+
+    handleMouseLeaveWrapper = () => {
+        this.foundation.handleMouseLeaveWrapper();
+    }
+
+    handleTimeChange = (value: number) => {
+        this.foundation.handleTimeChange(value);
+    }
+
+    handleTimeUpdate = () => {
+        this.foundation.handleTimeUpdate();
+    }
+
+    handleError = () => {
+        this.foundation.handleError();
+    }
+
+    handlePlay = () => {
+        this.foundation.handlePlay();
+    }
+
+    handlePause = () => {
+        this.foundation.handlePause();
+    }
+
+    handleCanPlay = () => {
+        this.foundation.handleCanPlay();
+    }
+
+    handleWaiting = (locale: Locale['VideoPlayer']) => {
+        this.foundation.handleWaiting(locale);
+    }
+
+    handleStalled = (locale: Locale['VideoPlayer']) => {
+        this.foundation.handleStalled(locale);
+    }
+
+    handleProgress = () => {
+        this.foundation.handleProgress();
+    }
+
+    handleEnded = () => {
+        this.foundation.handleEnded();
+    }
+
+    handleDurationChange = () => {
+        this.foundation.handleDurationChange();
+    }
+
+    handleVolumeChange = (value: number) => {
+        this.foundation.handleVolumeChange(value);
+    }
+
+    handleVolumeSilent = () => {
+        this.foundation.handleVolumeSilent();
+    }
+
+    handleRateChange = (option: { label: string; value: number }, locale: Locale['VideoPlayer']) => {
+        this.foundation.handleRateChange(option, locale);
+    }
+
+    handleQualityChange = (option: { label: string; value: string }, locale: Locale['VideoPlayer']) => {
+        this.foundation.handleQualityChange(option, locale);
+    }
+
+    handleRouteChange = (option: { label: string; value: string }, locale: Locale['VideoPlayer']) => {
+        this.foundation.handleRouteChange(option, locale);
+    }
+    
+    handleMirror = (locale: Locale['VideoPlayer']) => {
+        this.foundation.handleMirror(locale);
+    }
+
+    handleFullscreen = () => {
+        this.foundation.handleFullscreen();
+    }
+
+    handlePictureInPicture = () => {
+        this.foundation.handlePictureInPicture();
+    }
+
+    getVolumeIcon = () => {
+        const { volume, muted } = this.state;
+        if (muted) {
+            return <IconMute />;
+        }
+        if (volume < 50) {
+            return <IconVolume1 />;
+        }
+        return <IconVolume2 />;
+    }
+
+    isResourceNotFound = () => {
+        const { src } = this.props;
+        return isNullOrUndefined(src);
+    }
+
+    renderTime = () => {
+        const { currentTime, totalTime } = this.state;
+        if (this.foundation.shouldShowControlItem(strings.TIME)) {
+            return <div className={cls(`${cssClasses.PREFIX_CONTROLS}-time`)}>
+                {formatTime(currentTime)} / {formatTime(totalTime)}
+            </div>;
+        }
+        return null;
+    }
+
+    renderResourceNotFound = (locale: Locale['VideoPlayer']) => {
+        return (
+            <div className={cls(`${prefixCls}-resource-not-found`)}>
+                {locale.noResource}
+            </div>
+        );
+    }
+
+    renderPauseIcon = () => {
+        const { isPlaying, isError } = this.state;
+        if (!isPlaying && !isError) {
+            return (
+                // eslint-disable-next-line jsx-a11y/no-static-element-interactions
+                <div className={cls(`${prefixCls}-pause`)} >
+                    <IconPlayCircle />
+                </div>
+            );
+        }
+        return null;
+    }
+
+    renderError = (locale: Locale['VideoPlayer']) => {
+        const { isError } = this.state;
+        const { theme } = this.props;
+        if (isError) {
+            return (
+                <div className={cls(`${prefixCls}-error`, 
+                    { [`${prefixCls}-error-${theme}`]: theme })}
+                >
+                    <div className={cls(`${prefixCls}-error-svg`)}>
+                        <ErrorSVG />
+                    </div>
+                    {locale.videoError}
+                </div>
+            );
+        }
+        return null;
+    }
+
+    renderPoster = () => {
+        const { poster } = this.props;
+        const { isPlaying, currentTime, totalTime } = this.state;
+        const isHide = currentTime > 0 && currentTime < totalTime;
+        if (!isPlaying && poster) {
+            return (
+                <img 
+                    className={cls(`${prefixCls}-poster`, 
+                        { [`${prefixCls}-poster-hide`]: isHide }
+                    )}
+                    src={poster} 
+                    alt="poster" 
+                />
+            );
+        }
+        return null;
+    }
+
+    renderNotification = () => {
+        const { showNotification, notificationContent } = this.state;
+
+        if (!showNotification || !notificationContent) {
+            return null;
+        }
+
+        return (
+            <div className={cls(`${prefixCls}-notification`)}>
+                {this.state.notificationContent}
+            </div>
+        );
+    }
+
+    renderVolume = () => {
+        const { volume, muted } = this.state;
+        if (this.foundation.shouldShowControlItem(strings.VOLUME)) {
+            return (
+                <Popover 
+                    autoAdjustOverflow
+                    position='top'
+                    className={cls(`${cssClasses.PREFIX_CONTROLS}-popover`)}
+                    content={
+                        <div className={cls(`${cssClasses.PREFIX_CONTROLS}-volume`)}>
+                            <div className={cls(`${cssClasses.PREFIX_CONTROLS}-volume-title`)}>{muted ? 0 : volume}%</div>
+                            <AudioSlider 
+                                value={muted ? 0 : volume} 
+                                max={100} 
+                                vertical 
+                                height={120} 
+                                showTooltip={false} 
+                                onChange={this.handleVolumeChange} 
+                            />
+                        </div>
+                    }
+                >
+                    <Button
+                        className={cls(
+                            `${cssClasses.PREFIX_CONTROLS}-menu-item`, 
+                            `${cssClasses.PREFIX_CONTROLS}-menu-button`)
+                        }
+                        theme={'borderless'}
+                        icon={this.getVolumeIcon()}
+                        onClick={this.handleVolumeSilent}
+                    />
+                </Popover>
+            );
+        }
+        return null;
+    }
+
+    renderIconButton = (icon: React.ReactNode, onClick: () => void, name: string) => {
+        if (!this.foundation.shouldShowControlItem(name)) {
+            return null;
+        }
+        return (
+            <Button
+                theme={'borderless'}
+                className={cls(
+                    `${cssClasses.PREFIX_CONTROLS}-menu-item`, 
+                    `${cssClasses.PREFIX_CONTROLS}-menu-button`
+                )}
+                icon={icon}
+                onClick={onClick}
+            />
+        );
+    }
+
+    renderDropdownButton = (currentValue: string | number, list: { label: string; value: number | string }[], handleChange: (option: { label: string; value: any }, locale: Locale['VideoPlayer']) => void, name: string, locale: Locale['VideoPlayer']) => {
+        if (this.foundation.shouldShowControlItem(name)) {
+            return (
+                <Dropdown 
+                    position='top'
+                    className={cls(`${cssClasses.PREFIX_CONTROLS}-popup-menu`)} 
+                    render={
+                        <Dropdown.Menu>
+                            {list.map((option) => (
+                                <Dropdown.Item 
+                                    className={cls(`${cssClasses.PREFIX_CONTROLS}-popup-menu-item`)} 
+                                    key={option.value} 
+                                    onClick={() => handleChange(option, locale)} 
+                                    active={option.value === currentValue}
+                                >
+                                    {option.label}
+                                </Dropdown.Item>
+                            ))}
+                        </Dropdown.Menu>
+                    } 
+                    onChange={(option: { label: string; value: any }) => handleChange(option, locale)}
+                >
+                    <div 
+                        className={cls(
+                            `${cssClasses.PREFIX_CONTROLS}-menu-item`, 
+                            `${cssClasses.PREFIX_CONTROLS}-popup`)
+                        }
+                    >
+                        {list.find((option) => option.value === currentValue)?.label}
+                    </div>
+                </Dropdown>
+            );
+        }
+        return null;
+    }
+
+    render() {
+        const { markers, qualityList, routeList, width, height, autoPlay, style, className, loop, captionsSrc, crossOrigin, theme } = this.props;
+        const { isPlaying, playbackRate, playbackRateList, isMirror, currentTime, totalTime, currentQuality, currentRoute, src, bufferedValue, showControls } = this.state;
+
+        return (
+            <LocaleConsumer componentName="VideoPlayer">
+                {(locale: Locale['VideoPlayer']) => { 
+                    return (
+                        <div 
+                            className={cls(`${prefixCls}`,
+                                className,
+                                { [`${prefixCls}-mirror`]: isMirror },
+                            )}
+                            style={{ width, height, ...style }}
+                            ref={this.videoWrapperRef}
+                            onMouseEnter={this.handleMouseEnterWrapper}
+                            onMouseLeave={this.handleMouseLeaveWrapper} 
+                        >
+                            <div className={cls(`${prefixCls}-wrapper`,
+                                { [`${cssClasses.PREFIX}-wrapper-${theme}`]: theme }
+                            )}>
+                                <video 
+                                    ref={this.videoRef} 
+                                    autoPlay={autoPlay}
+                                    loop={loop}
+                                    controls={false}
+                                    crossOrigin={crossOrigin}
+                                    src={src}
+                                    onTimeUpdate={this.handleTimeUpdate}
+                                    onDurationChange={this.handleDurationChange}
+                                    onClick={() => { this.foundation.handlePlayOrPause();}}
+                                    // An error occurred while getting the media data, or the resource is in an unsupported format.
+                                    onError={this.handleError}
+                                    onCanPlay={this.handleCanPlay}
+                                    // Playback stopped due to temporary lack of data.
+                                    onWaiting={() => this.handleWaiting(locale)}
+                                    // The user agent attempted to fetch media data but was unexpectedly unable to fetch the data.
+                                    onStalled={() => this.handleStalled(locale)}
+                                    onProgress={this.handleProgress}
+                                    onEnded={this.handleEnded}
+                                >
+                                    <track kind="captions" src={captionsSrc}/>
+                                </video>
+                                {this.isResourceNotFound() && this.renderResourceNotFound(locale)}
+                            </div>
+                            {this.renderPoster()}
+                            {this.renderPauseIcon()}
+                            {this.renderError(locale)}
+                            {this.renderNotification()}
+                            <div className={cls(`${cssClasses.PREFIX_CONTROLS}`,
+                                { [`${cssClasses.PREFIX_CONTROLS}-hide`]: !showControls }
+                            )}>
+                                <VideoProgress 
+                                    key={totalTime}
+                                    value={currentTime} 
+                                    max={totalTime} 
+                                    onChange={this.handleTimeChange}
+                                    markers={markers}
+                                    bufferedValue={bufferedValue}
+                                />
+                                <div className={cls(`${cssClasses.PREFIX_CONTROLS}-menu`)}>
+                                    <div className={cls(`${cssClasses.PREFIX_CONTROLS}-menu-left`)}>
+                                        {this.renderIconButton(isPlaying ? <IconPause /> : <IconPlay />, isPlaying ? this.handlePause : this.handlePlay, strings.PLAY)}
+                                        {this.renderIconButton(<IconRestart rotate={180} />, isPlaying ? this.handlePause : this.handlePlay, strings.NEXT)}
+                                        {this.renderTime()}
+                                        {this.renderVolume()}
+                                        {this.renderDropdownButton(playbackRate, playbackRateList, this.handleRateChange, strings.PLAYBACK_RATE, locale)}
+                                    </div>
+                                    <div className={cls(`${cssClasses.PREFIX_CONTROLS}-menu-right`)}>
+                                        {qualityList && qualityList.length > 0 && this.renderDropdownButton(currentQuality, qualityList, this.handleQualityChange, strings.QUALITY, locale)}
+                                        {routeList && routeList.length > 0 && this.renderDropdownButton(currentRoute, routeList, this.handleRouteChange, strings.ROUTE, locale)}
+                                        {this.renderIconButton(<IconFlipHorizontal />, () => this.handleMirror(locale), strings.MIRROR)}
+                                        {this.renderIconButton(this.foundation.checkFullScreen() ? <IconMinimize /> : <IconMaximize />, this.handleFullscreen, strings.FULLSCREEN)}
+                                        {this.renderIconButton(<IconMiniPlayer />, this.handlePictureInPicture, strings.PICTURE_IN_PICTURE)}
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    );
+                }}
+            </LocaleConsumer>
+        );
+    }
+}
+
+export default VideoPlayer; 

+ 15 - 0
packages/semi-ui/videoPlayer/utils.ts

@@ -0,0 +1,15 @@
+export const formatTime = (time: number) => {
+    if (isNaN(time)) {
+        return '00:00';
+    }
+    const hours = Math.floor(time / 3600);
+    if (hours > 0) {
+        const minutes = Math.floor((time - hours * 3600) / 60);
+        const seconds = Math.floor(time % 60);
+        return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
+    } else {
+        const minutes = Math.floor(time / 60);
+        const seconds = Math.floor(time % 60);
+        return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
+    }
+};

+ 193 - 0
packages/semi-ui/videoPlayer/videoProgress.tsx

@@ -0,0 +1,193 @@
+import React from 'react';
+import cls from 'classnames';
+import '@douyinfe/semi-foundation/videoPlayer/videoPlayer.scss';
+import { cssClasses } from '@douyinfe/semi-foundation/videoPlayer/constants';
+import VideoProgressFoundation, { Marker, MarkerListItem, VideoProgressAdapter } from '@douyinfe/semi-foundation/videoPlayer/progressFoundation';
+import Tooltip from '../tooltip';
+import { noop } from 'lodash';
+import { formatTime } from './utils';
+import BaseComponent from '../_base/baseComponent';
+
+export interface VideoProgressProps {
+    value: number;
+    onChange: (value: number) => void;
+    className?: string;
+    max: number;
+    showTooltip?: boolean;
+    markers?: Marker[];
+    bufferedValue: number
+}
+
+export interface VideoProgressState {
+    isDragging: boolean;
+    isHandleHovering: boolean;
+    movingInfo: { progress: number; offset: number; value: number } | null;
+    activeIndex: number
+}
+
+export default class VideoProgress extends BaseComponent<VideoProgressProps, VideoProgressState> {
+    static defaultProps = {
+        value: 0,
+        onChange: noop,
+        max: 100,
+        showTooltip: true,
+    };
+
+    private sliderRef: React.RefObject<HTMLDivElement>;
+    private handleRef: React.RefObject<HTMLDivElement>;
+    private markersList: MarkerListItem[];
+    foundation: VideoProgressFoundation;
+
+    constructor(props: VideoProgressProps) {
+        super(props);
+        this.state = {
+            isDragging: false,
+            isHandleHovering: false,
+            movingInfo: null,
+            activeIndex: -1, // Used to determine which slider the current handle is on under the dragging state
+        };
+
+        this.sliderRef = React.createRef();
+        this.handleRef = React.createRef();
+        this.markersList = this.initMarkerList();
+        this.foundation = new VideoProgressFoundation(this.adapter);
+    }
+
+    get adapter(): VideoProgressAdapter<VideoProgressProps, VideoProgressState> {
+        return {
+            ...super.adapter,
+            getSliderRef: () => this.sliderRef.current,
+            getMarkersList: () => this.markersList,
+            setIsDragging: (isDragging: boolean) => this.setState({ isDragging }),
+            setIsHandleHovering: (isHandleHovering: boolean) => this.setState({ isHandleHovering }),
+            setActiveIndex: (activeIndex: number) => this.setState({ activeIndex }),
+            setMovingInfo: (movingInfo: { progress: number; offset: number; value: number } | null) => this.setState({ movingInfo }),
+        };
+    }
+
+    initMarkerList = () => {
+        const { markers, max } = this.props;
+        const hasMarkers = markers && markers.length > 0;
+        const defaultMarker: MarkerListItem = {
+            start: 0,
+            end: max,
+            left: '0',
+            title: '',
+            width: '100%',
+        };
+        const newMarkers = hasMarkers ? [...markers] : [defaultMarker];
+        let markersList: MarkerListItem[] = [];
+        if (hasMarkers) {
+            newMarkers.forEach((marker: MarkerListItem | Marker, index: number) => {
+                const end = index === newMarkers.length - 1 ? max : newMarkers[index + 1].start;
+                if (!(marker.start > max || end > max)) {
+                    const item = {
+                        left: `${(marker.start / max) * 100}%`,
+                        width: `${max ? (end - marker.start) / max * 100 : 100}%`,
+                        end: end,
+                        start: marker.start,
+                        title: marker.title
+                    };
+                    markersList.push(item);
+                }
+            });
+        } else {
+            markersList.push(defaultMarker);
+        }
+        return markersList;
+    }
+
+    handleMouseEnter = (e: any) => {
+        this.foundation.handleMouseEvent(e, false);
+    }
+
+    handleMouseMove = (e: any) => {
+        this.foundation.handleMouseEvent(e, true);
+    }
+
+    renderTooltipContent = () => {
+        const { movingInfo } = this.state;
+        if (this.markersList.length > 0 && movingInfo) {
+            const hoverIndex = this.markersList.findIndex((marker: MarkerListItem) => {
+                return movingInfo?.value > marker.start && movingInfo?.value < marker.end;
+            });
+            return (
+                <>
+                    <div className={cls(`${cssClasses.PREFIX_PROGRESS}-tooltip-content`)}>
+                        {this.markersList[hoverIndex]?.title}
+                    </div>
+                    <div className={cls(`${cssClasses.PREFIX_PROGRESS}-tooltip-content`)}>
+                        {formatTime(movingInfo.progress * this.props.max)}
+                    </div>
+                </>
+            );
+        }
+        return movingInfo && formatTime(movingInfo.progress * this.props.max);
+    }
+
+    render() {
+        const { showTooltip, max, value: currentValue } = this.props;
+        const { movingInfo, isHandleHovering, isDragging, activeIndex } = this.state;
+        const sliderContent = (
+            <div
+                role="slider"
+                tabIndex={0}
+                aria-valuenow={currentValue as number}
+                ref={this.sliderRef}
+                className={cls(`${cssClasses.PREFIX_PROGRESS}`)}
+                onMouseDown={this.foundation.handleMouseDown}
+                onMouseUp={this.foundation.handleMouseUp}
+                onMouseEnter={this.handleMouseEnter}
+                onMouseMove={this.handleMouseMove}
+            >
+                <div className={cls(`${cssClasses.PREFIX_PROGRESS}-markers`)}>
+                    {
+                        this.markersList.map((marker: MarkerListItem, index: number) => (
+                            <div
+                                key={`${marker.start}-${index}`}   
+                                className={cls(`${cssClasses.PREFIX_PROGRESS}-slider`,
+                                    { [`${cssClasses.PREFIX_PROGRESS}-slider-active`]: index === activeIndex && isDragging }
+                                )}
+                                style={{ left: marker.left, width: marker.width }}
+                                onMouseEnter={() => this.foundation.handleSliderMouseEnter(index)}
+                                onMouseLeave={() => this.foundation.handleSliderMouseLeave(index)}
+                            >
+                                <div className={cls(`${cssClasses.PREFIX_PROGRESS}-slider-list`)} />
+                                <div
+                                    className={cls(`${cssClasses.PREFIX_PROGRESS}-slider-buffered`)}
+                                    style={{ width: this.foundation.getLoadedWidth(marker) }}
+                                />
+                                <div
+                                    className={cls(`${cssClasses.PREFIX_PROGRESS}-slider-played`)}
+                                    style={{ width: this.foundation.getPlayedWidth(marker) }}
+                                />
+                            </div>
+                        ))
+                    }
+                </div>
+                <div
+                    ref={this.handleRef}
+                    className={cls(`${cssClasses.PREFIX_PROGRESS}-handle`)}
+                    style={{
+                        left: `calc(${((max ? (currentValue || 1) / max : 0) * 100)}% - 8px)`,
+                        transform: 'translateY(-50%)',
+                        opacity: (isHandleHovering || isDragging) ? 1 : 0,
+                        transition: 'opacity 0.3s',
+                        pointerEvents: 'none',
+                    }}
+                />
+            </div>
+        );
+
+        return showTooltip ? (
+            <Tooltip
+                position={'top'}
+                className={cls(`${cssClasses.PREFIX_PROGRESS}-tooltip`)}
+                content={this.renderTooltipContent()}
+                style={{ 'left': movingInfo?.offset }}
+            >
+                {sliderContent}
+            </Tooltip>
+        ) : sliderContent;
+    }
+}

+ 1 - 1
packages/semi-webpack/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-webpack-plugin",
-    "version": "2.79.0",
+    "version": "2.80.0-beta.0",
     "description": "",
     "author": "伍浩威 <[email protected]>",
     "homepage": "",

文件差异内容过多而无法显示
+ 197 - 197
sitemap.xml


+ 3 - 0
src/images/docIcons/doc-videoplayer.svg

@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M1 6C1 4.89543 1.89543 4 3 4H14C15.1046 4 16 4.89543 16 6V18C16 19.1046 15.1046 20 14 20H3C1.89543 20 1 19.1046 1 18V6ZM4.9823 8H7.9823V11H4.9823V8ZM23.0001 7.6185C23.0001 6.875 22.2176 6.39151 21.5526 6.72418L17.5565 8.72354C17.2178 8.89298 17.0039 9.23918 17.0039 9.61786V14.3821C17.0039 14.7608 17.2178 15.107 17.5565 15.2765L21.5526 17.2758C22.2176 17.6085 23.0001 17.125 23.0001 16.3815V7.6185Z" fill="#AAB2BF"/>
+</svg>

+ 28 - 101
yarn.lock

@@ -1585,25 +1585,11 @@
     "@douyinfe/semi-animation-styled" "2.65.0"
     classnames "^2.2.6"
 
-"@douyinfe/[email protected]":
-  version "2.78.0"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-react/-/semi-animation-react-2.78.0.tgz#25db969d363b649052c809fc9a7f1a721a51c915"
-  integrity sha512-81DUulQUnjjuWeAw73gOay+BHLEHchCcvGhyWZVL1s/CYi73AoivmraWc5GrCWdZzT8nMxSAJ85hesXnwGVHgg==
-  dependencies:
-    "@douyinfe/semi-animation" "2.78.0"
-    "@douyinfe/semi-animation-styled" "2.78.0"
-    classnames "^2.2.6"
-
 "@douyinfe/[email protected]":
   version "2.65.0"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-styled/-/semi-animation-styled-2.65.0.tgz#8c56047a5704a45b05cc9809a2a126cc24526ea1"
   integrity sha512-YFF8Ptcz/jwS0phm28XZV7ROqMQ233sjVR0Uy33FImCITr6EAPe5wcCeEmzVZoYS7x3tUFR30SF+0hSO01rQUg==
 
-"@douyinfe/[email protected]":
-  version "2.78.0"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-styled/-/semi-animation-styled-2.78.0.tgz#e3d9fd66625bd3d46105fc72fe6e0c2ca47775dc"
-  integrity sha512-NYMuI4SOl4+fV4/S8qJfL8ip1EkjO+G6rtlWTmtvc77ENHoBsf6C+qLxupi5e+IKGBy/Vfr52hPDOsOC8FWokQ==
-
 "@douyinfe/[email protected]":
   version "2.65.0"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation/-/semi-animation-2.65.0.tgz#f544a6b420c3e948c09836019e6b63f1382cd12c"
@@ -1611,13 +1597,6 @@
   dependencies:
     bezier-easing "^2.1.0"
 
-"@douyinfe/[email protected]":
-  version "2.78.0"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation/-/semi-animation-2.78.0.tgz#a76be1ff65486d5146e78fdafffd5a6ab1b818e6"
-  integrity sha512-M54Typ1mHU4BIT/em/WlpmKM0V1yk2EFoVy9qtTr0+vkIgTfE+w53A7/NX2cyWq97FHCD9k4jQGGK31Z1YqMQg==
-  dependencies:
-    bezier-easing "^2.1.0"
-
 "@douyinfe/[email protected]":
   version "2.65.0"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-foundation/-/semi-foundation-2.65.0.tgz#20466a9b4baacdde2249930fb709ba035c5a7bea"
@@ -1637,26 +1616,6 @@
     remark-gfm "^4.0.0"
     scroll-into-view-if-needed "^2.2.24"
 
-"@douyinfe/[email protected]":
-  version "2.78.0"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-foundation/-/semi-foundation-2.78.0.tgz#d1949b552ff75bfe66a88519fdfbf97dfd71216f"
-  integrity sha512-LVBcVUDM74FMmzx/L6b/Vk/W9b4MnQCb1+1t7Q2BU3v5DC4dfhgRpbloxj3JmAyOgAcxJz2VDE07Sgzt9mS6ww==
-  dependencies:
-    "@douyinfe/semi-animation" "2.78.0"
-    "@douyinfe/semi-json-viewer-core" "2.78.0"
-    "@mdx-js/mdx" "^3.0.1"
-    async-validator "^3.5.0"
-    classnames "^2.2.6"
-    date-fns "^2.29.3"
-    date-fns-tz "^1.3.8"
-    fast-copy "^3.0.1 "
-    lodash "^4.17.21"
-    lottie-web "^5.12.2"
-    memoize-one "^5.2.1"
-    prismjs "^1.29.0"
-    remark-gfm "^4.0.0"
-    scroll-into-view-if-needed "^2.2.24"
-
 "@douyinfe/[email protected]", "@douyinfe/semi-icons@latest":
   version "2.65.0"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-icons/-/semi-icons-2.65.0.tgz#af39cbd5431ebccedcf7d9ce689646e54bebc432"
@@ -1664,30 +1623,11 @@
   dependencies:
     classnames "^2.2.6"
 
-"@douyinfe/[email protected]", "@douyinfe/semi-icons@^2.0.0":
-  version "2.78.0"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-icons/-/semi-icons-2.78.0.tgz#aa0f852939f9eef071d17d2c3894dabf2e4b87d4"
-  integrity sha512-8Sugn7lgAqgHThX3EJT9npyQ2tXFIEZoV6KkYMNFdewOlZ23O1VYEHwh4ktdNVsgtw5CTQ5+UQrsuM3mqszAVw==
-  dependencies:
-    classnames "^2.2.6"
-
 "@douyinfe/[email protected]":
   version "2.65.0"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-illustrations/-/semi-illustrations-2.65.0.tgz#9916c540c91222a1d9f48cd34a941d28b8a05d2f"
   integrity sha512-1IhOztyBYiSu8WrcvN+oWWtcJTC9+x6zbnYtufx4ToISs5UO1te1PQofABpkDzIJYFtW9yYLxg4uoL4wGjqYMA==
 
-"@douyinfe/[email protected]":
-  version "2.78.0"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-illustrations/-/semi-illustrations-2.78.0.tgz#af2a6188d36b156bb716005a4ee01419eadf4b06"
-  integrity sha512-690Llbnqsf1OZYuFvHhFWLpJkj6Gk5fjnWjJzUUcgfS5Apn/FBRHH2tkGyn4eU2iBihta1zzC6/G7Orp7qID5A==
-
-"@douyinfe/[email protected]":
-  version "2.78.0"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-json-viewer-core/-/semi-json-viewer-core-2.78.0.tgz#046b07c4f51db386478175f661484f5f147fb8b5"
-  integrity sha512-htbOfZp079wxoKaZkGwxd7pFfxU+tfzihGMmTy3b3Dneut3cPQmRVkyCSbJjuYwOkNEyHQ7Cva6KFbNhfeKxqw==
-  dependencies:
-    jsonc-parser "^3.3.1"
-
 "@douyinfe/[email protected]":
   version "2.23.2"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-scss-compile/-/semi-scss-compile-2.23.2.tgz#30884bb194ee9ae1e81877985e5663c3297c1ced"
@@ -1759,39 +1699,6 @@
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-theme-default/-/semi-theme-default-2.61.0.tgz#a7e9bf9534721c12af1d0eeb5d5a2de615896a23"
   integrity sha512-obn/DOw4vZyKFAlWvZxHTpBLAK9FO9kygTSm2GROgvi+UDB2PPU6l20cuUCsdGUNWJRSqYlTTVZ1tNYIyFZ5Sg==
 
-"@douyinfe/[email protected]":
-  version "2.78.0"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-theme-default/-/semi-theme-default-2.78.0.tgz#009d711196b5a15134afbffc14c088368db1e058"
-  integrity sha512-LtF6G+cmGNNjDE08K5VCG2n2cOi/hPIBTCfvwwXot8druEw94RGQ16rnAKNDMhASWJbiAzaVWKGhDQLDCwfZvg==
-
-"@douyinfe/semi-ui@^2.0.0":
-  version "2.78.0"
-  resolved "https://registry.yarnpkg.com/@douyinfe/semi-ui/-/semi-ui-2.78.0.tgz#5a8fdcac557932e3e24c3c495ef81f98638baadc"
-  integrity sha512-S8AvtfwLwb147mRzX83WmnW4op5YXoD0RSdnDzWrWDNNQyszV7VkV6+f2VIg27e4rReexYkMG1SzJLdzgoZKGQ==
-  dependencies:
-    "@dnd-kit/core" "^6.0.8"
-    "@dnd-kit/sortable" "^7.0.2"
-    "@dnd-kit/utilities" "^3.2.1"
-    "@douyinfe/semi-animation" "2.78.0"
-    "@douyinfe/semi-animation-react" "2.78.0"
-    "@douyinfe/semi-foundation" "2.78.0"
-    "@douyinfe/semi-icons" "2.78.0"
-    "@douyinfe/semi-illustrations" "2.78.0"
-    "@douyinfe/semi-theme-default" "2.78.0"
-    async-validator "^3.5.0"
-    classnames "^2.2.6"
-    copy-text-to-clipboard "^2.1.1"
-    date-fns "^2.29.3"
-    date-fns-tz "^1.3.8"
-    fast-copy "^3.0.1 "
-    jsonc-parser "^3.3.1"
-    lodash "^4.17.21"
-    prop-types "^15.7.2"
-    react-resizable "^3.0.5"
-    react-window "^1.8.2"
-    scroll-into-view-if-needed "^2.2.24"
-    utility-types "^3.10.0"
-
 "@douyinfe/semi-ui@latest":
   version "2.65.0"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-ui/-/semi-ui-2.65.0.tgz#295eb0dd8e9e961adb4ddd7c7bbce3468d1b7430"
@@ -12074,11 +11981,6 @@ eslint-plugin-react@^7.20.6, eslint-plugin-react@^7.24.0:
     string.prototype.matchall "^4.0.11"
     string.prototype.repeat "^1.0.0"
 
-eslint-plugin-semi-design@^2.33.0:
-  version "2.78.0"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-semi-design/-/eslint-plugin-semi-design-2.78.0.tgz#066c79b8e2e0bbb0cc7d8ed2ac8811dd601bd289"
-  integrity sha512-1+ZltNfA/zx1ipMaqa+gr/yTArpx2aeaXb4UXz6omUzlmgFDERbIUZ1P1n6+bbOCu8tAJ4CaD3gnwlDFBcamGQ==
-
 eslint-rule-composer@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9"
@@ -25284,7 +25186,7 @@ string-similarity@^1.2.2:
     lodash.map "^4.6.0"
     lodash.maxby "^4.6.0"
 
-"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0":
   version "4.2.3"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
   integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -25302,6 +25204,15 @@ string-width@^1.0.1, string-width@^1.0.2:
     is-fullwidth-code-point "^1.0.0"
     strip-ansi "^3.0.0"
 
+"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
+  version "4.2.3"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+  integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+  dependencies:
+    emoji-regex "^8.0.0"
+    is-fullwidth-code-point "^3.0.0"
+    strip-ansi "^6.0.1"
+
 string-width@^2.0.0, string-width@^2.1.0:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
@@ -25445,7 +25356,7 @@ stringify-object@^3.3.0:
     is-obj "^1.0.1"
     is-regexp "^1.0.0"
 
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
   integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -25473,6 +25384,13 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
   dependencies:
     ansi-regex "^4.1.0"
 
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+  integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+  dependencies:
+    ansi-regex "^5.0.1"
+
 strip-ansi@^7.0.1:
   version "7.1.0"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -28067,7 +27985,7 @@ worker-loader@^3.0.8:
     loader-utils "^2.0.0"
     schema-utils "^3.0.0"
 
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
   integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -28102,6 +28020,15 @@ wrap-ansi@^6.2.0:
     string-width "^4.1.0"
     strip-ansi "^6.0.0"
 
+wrap-ansi@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+  integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+  dependencies:
+    ansi-styles "^4.0.0"
+    string-width "^4.1.0"
+    strip-ansi "^6.0.0"
+
 wrap-ansi@^8.1.0:
   version "8.1.0"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"

部分文件因为文件数量过多而无法显示