├── .editorconfig ├── .fatherrc.ts ├── .gitignore ├── .redbudrc.base.ts ├── .umirc.ts ├── LICENSE ├── README.md ├── build.sh ├── docs ├── guide │ └── getting-started.md └── index.md ├── lerna.json ├── now.json ├── package.json ├── packages ├── aliplayer │ ├── .redbudrc.ts │ ├── CHANGELOG.md │ ├── README.md │ ├── docs │ │ ├── aliplayer.md │ │ └── demos │ │ │ ├── demo-01.tsx │ │ │ ├── demo-02.tsx │ │ │ ├── demo-03.tsx │ │ │ └── demo-04.tsx │ ├── package.json │ └── src │ │ ├── index.less │ │ ├── index.tsx │ │ ├── player.tsx │ │ ├── types.ts │ │ └── typings.d.ts ├── responsive-card │ ├── .redbudrc.ts │ ├── CHANGELOG.md │ ├── README.md │ ├── demo │ │ ├── demo01.tsx │ │ ├── demo02.tsx │ │ └── demo03.tsx │ ├── docs │ │ └── responsive-card.md │ ├── package.json │ └── src │ │ ├── index.tsx │ │ └── utils.ts └── split-screen │ ├── .redbudrc.ts │ ├── CHANGELOG.md │ ├── README.md │ ├── docs │ ├── demo │ │ ├── demo-01.tsx │ │ ├── demo-02.tsx │ │ ├── demo-03.less │ │ └── demo-03.tsx │ └── split-screen.md │ ├── package.json │ └── src │ ├── index.tsx │ ├── types.ts │ └── utils.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── renovate.json └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /.fatherrc.ts: -------------------------------------------------------------------------------- 1 | import { readdirSync } from 'fs'; 2 | import { join } from 'path'; 3 | 4 | // utils must build before core 5 | // runtime must build before renderer-react 6 | const headPkgs: string[] = []; 7 | const tailPkgs = readdirSync(join(__dirname, 'packages')).filter( 8 | (pkg) => pkg.charAt(0) !== '.' && !headPkgs.includes(pkg), 9 | ); 10 | 11 | export default { 12 | cjs: { 13 | type: 'babel', 14 | lazy: true 15 | }, 16 | esm: { 17 | type: 'babel', 18 | importLibToEs: true, 19 | }, 20 | pkgs: [...headPkgs, ...tailPkgs], 21 | extraBabelPlugins: [ 22 | ['babel-plugin-import', { libraryName: 'antd', libraryDirectory: 'es', style: true }, 'antd'], 23 | ], 24 | }; 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Log 2 | npm-debug.log* 3 | yarn-debug.log* 4 | yarn-error.log* 5 | 6 | # Test 7 | coverage 8 | 9 | # Dependency directories 10 | node_modules 11 | 12 | package-lock.json 13 | 14 | # build output 15 | es 16 | lib 17 | dist 18 | 19 | # IDE 20 | .idea 21 | .vscode 22 | 23 | .DS_Store 24 | 25 | # Umi 26 | .umi 27 | .umi-production 28 | 29 | # Local 30 | local.config.ts 31 | yarn.lock 32 | install-state.gz 33 | -------------------------------------------------------------------------------- /.redbudrc.base.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | esm: { 3 | output: 'es', 4 | }, 5 | cjs: { 6 | output: 'lib', 7 | }, 8 | platform: 'browser' 9 | }; 10 | -------------------------------------------------------------------------------- /.umirc.ts: -------------------------------------------------------------------------------- 1 | import { readdirSync } from 'fs'; 2 | import chalk from 'chalk'; 3 | import { join } from 'path'; 4 | 5 | const headPkgList = []; 6 | // utils must build before core 7 | // runtime must build before renderer-react 8 | const pkgList = readdirSync(join(__dirname, 'packages')).filter( 9 | (pkg) => pkg.charAt(0) !== '.' && !headPkgList.includes(pkg), 10 | ); 11 | 12 | const alias = pkgList.reduce((pre, pkg) => { 13 | pre[`@pansy/react-${pkg}`] = join(__dirname, 'packages', pkg, 'src'); 14 | return { 15 | ...pre, 16 | }; 17 | }, {}); 18 | 19 | console.log(`🌼 alias list \n${chalk.blue(Object.keys(alias).join('\n'))}`); 20 | 21 | const tailPkgList = pkgList 22 | .map((path) => [join('packages', path, 'docs')]) 23 | .reduce((acc, val) => acc.concat(val), []); 24 | 25 | const logo = 'https://cdn.jsdelivr.net/gh/wangxingkang/pictures@latest/imgs/react.svg'; 26 | 27 | export default { 28 | title: 'React Components', 29 | logo, 30 | favicon: logo, 31 | mode: 'site', 32 | alias, 33 | resolve: { includes: [...tailPkgList, 'docs'] }, 34 | navs: [ 35 | null, 36 | { 37 | title: 'AMap', 38 | path: 'https://react-amap-pansyjs.vercel.app/', 39 | }, 40 | { 41 | title: 'GitHub', 42 | path: 'https://github.com/pansyjs/react-components', 43 | }, 44 | ], 45 | hash: true, 46 | dynamicImport: {} 47 | }; 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Pansy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

React Components

2 | 3 |

React components library.

4 | 5 | ## 📦 组件看板 6 | 7 | | 组件 | 下载量 | 版本 | 8 | | --- | --- | --- | 9 | |[@pansy/react-watermark][npm-watermark-web]|[![npm download][npm-watermark-dw]][npm-watermark-url]|[![npm version][npm-watermark-v]][npm-watermark-url]| 10 | |[@pansy/react-aliplayer][npm-aliplayer-web]|[![npm download][npm-aliplayer-dw]][npm-aliplayer-url]|[![npm version][npm-aliplayer-v]][npm-aliplayer-url]| 11 | |[@pansy/react-split-screen][npm-ss-web]|[![npm download][npm-ss-dw]][npm-ss-url]|[![npm version][npm-ss-v]][npm-ss-url]| 12 | |[@pansy/react-responsive-card][npm-rc-web]|[![npm download][npm-rc-dw]][npm-rc-url]|[![npm version][npm-rc-v]][npm-rc-url]| 13 | 14 | ## 🌟 社区互助 15 | 16 | | Github Issue | 钉钉群 | 微信群 | 17 | | ------------------------------------------------- | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------- | 18 | | [issues](https://github.com/pansyjs/react-components/issues) | | | 19 | 20 | 21 | [npm-watermark-dw]: https://img.shields.io/npm/dw/@pansy/react-watermark.svg 22 | [npm-watermark-v]: https://img.shields.io/npm/v/@pansy/react-watermark.svg?style=flat-square?style=flat-square 23 | [npm-watermark-url]: https://www.npmjs.com/package/@pansy/react-watermark 24 | [npm-watermark-web]: https://watermark-eosin.vercel.app/packages/frames 25 | 26 | [npm-aliplayer-dw]: https://img.shields.io/npm/dw/@pansy/react-aliplayer.svg 27 | [npm-aliplayer-v]: https://img.shields.io/npm/v/@pansy/react-aliplayer.svg?style=flat-square?style=flat-square 28 | [npm-aliplayer-url]: https://www.npmjs.com/package/@pansy/react-aliplayer 29 | [npm-aliplayer-web]: https://react-components-vert.vercel.app/components/video/aliplayer 30 | 31 | [npm-ss-dw]: https://img.shields.io/npm/dw/@pansy/react-split-screen.svg 32 | [npm-ss-v]: https://img.shields.io/npm/v/@pansy/react-split-screen.svg?style=flat-square?style=flat-square 33 | [npm-ss-url]: https://www.npmjs.com/package/@pansy/react-split-screen 34 | [npm-ss-web]: https://react-components-vert.vercel.app/components/basic/split-screen 35 | 36 | [npm-rc-dw]: https://img.shields.io/npm/dw/@pansy/react-responsive-card.svg 37 | [npm-rc-v]: https://img.shields.io/npm/v/@pansy/react-responsive-card.svg?style=flat-square?style=flat-square 38 | [npm-rc-url]: https://www.npmjs.com/package/@pansy/react-responsive-card 39 | [npm-rc-web]: https://react-components-vert.vercel.app/components/basic/responsive-card 40 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | yarn 3 | yarn site 4 | -------------------------------------------------------------------------------- /docs/guide/getting-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 快速开始 3 | order: 2 4 | group: 5 | path: / 6 | nav: 7 | title: 文档 8 | path: /docs 9 | --- 10 | 11 | ## 安装 12 | 13 | 每一个组件都是一个独立的包,你需要在你的项目中安装对应的 npm 包并使用。 14 | 15 | ```sh 16 | $ npm i @pansy/react-watermark --save 17 | ``` 18 | 19 | 包含的组件: 20 | 21 | - @pansy/react-aliplayer 22 | - @pansy/react-split-screen 23 | - @pansy/react-watermark 24 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 介绍 - ProComponents 3 | order: 10 4 | sidebar: false 5 | hero: 6 | title: React Components 7 | desc: React components library. 8 | actions: 9 | - text: 快速开始 → 10 | link: /docs/getting-started-table 11 | 12 | features: 13 | - icon: https://gw.alipayobjects.com/os/q/cms/images/k9ziitmp/13668549-b393-42a2-97c3-a6365ba87ac2_w96_h96.png 14 | title: 简单易用 15 | desc: 简洁的API,开箱即用 16 | - icon: https://gw.alipayobjects.com/os/q/cms/images/k9ziip85/89434dcf-5f1d-4362-9ce0-ab8012a85924_w96_h96.png 17 | title: 国际化 18 | desc: 提供完备的国际化语言支持 19 | - icon: https://gw.alipayobjects.com/os/q/cms/images/k9zij2bh/67f75d56-0d62-47d6-a8a5-dbd0cb79a401_w96_h96.png 20 | title: TypeScript 21 | desc: 使用 TypeScript 开发,提供完整的类型定义文件 22 | 23 | footer: Open-source MIT Licensed | Copyright © 2017-present 24 | --- 25 | 26 | ## 📦 组件看板 27 | 28 | | 组件 | 下载量 | 版本 | 29 | | --- | --- | --- | 30 | | watermark | [![](https://img.shields.io/npm/dw/@pansy/react-watermark.svg)](https://www.npmjs.com/package/@pansy/react-watermark) | [![npm package](https://img.shields.io/npm/v/@pansy/react-watermark.svg?style=flat-square?style=flat-square)](https://www.npmjs.com/package/@pansy/react-watermark) | 31 | | aliplayer | [![](https://img.shields.io/npm/dw/@pansy/react-aliplayer.svg)](https://www.npmjs.com/package/@pansy/react-aliplayer) | [![npm package](https://img.shields.io/npm/v/@pansy/react-aliplayer.svg?style=flat-square?style=flat-square)](https://www.npmjs.com/package/@pansy/react-aliplayer) | 32 | | split-screen | [![](https://img.shields.io/npm/dw/@pansy/react-split-screen.svg)](https://www.npmjs.com/package/@pansy/react-split-screen) | [![npm package](https://img.shields.io/npm/v/@pansy/react-split-screen.svg?style=flat-square?style=flat-square)](https://www.npmjs.com/package/@pansy/react-split-screen) | 33 | 34 | ## 🌟 社区互助 35 | 36 | | Github Issue | 钉钉群 | 微信群 | 37 | | ------------------------------------------------- | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------- | 38 | | [issues](https://github.com/pansyjs/react-components/issues) | | | 39 | 40 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmClient": "yarn", 3 | "version": "independent", 4 | "useWorkspaces": true, 5 | "packages": [ 6 | "packages/*" 7 | ], 8 | "command": { 9 | "publish": { 10 | "npmClient": "npm", 11 | "verifyAccess": false, 12 | "ignoreChanges": ["*.md", "**/test/**"], 13 | "message": "chore(release): publish" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /now.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-components", 3 | "version": 2, 4 | "builds": [ 5 | { 6 | "src": "build.sh", 7 | "use": "@now/static-build", 8 | "config": { 9 | "distDir": "dist" 10 | } 11 | } 12 | ], 13 | "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pansy/react-components", 3 | "repository": "git@github.com:pansyjs/react-components.git", 4 | "author": "wangxingkang <156148958@qq.com>", 5 | "license": "MIT", 6 | "private": true, 7 | "scripts": { 8 | "start": "dumi dev", 9 | "build": "pnpm run -r build", 10 | "site": "dumi build", 11 | "test": "walrus test", 12 | "release": "walrus release" 13 | }, 14 | "devDependencies": { 15 | "@pansy/react-aliplayer": "workspace:*", 16 | "@ant-design/pro-card": "^2.1.10", 17 | "@types/react": "^18.0.27", 18 | "@types/react-dom": "^18.0.10", 19 | "@pansy/shared": "1.9.0", 20 | "@walrus/cli": "1.3.4", 21 | "@walrus/plugin-release": "1.14.3", 22 | "@walrus/plugin-test": "1.1.0", 23 | "antd": "5.1.7", 24 | "commitizen": "4.3.0", 25 | "cz-conventional-changelog": "3.3.0", 26 | "dumi": "1.1.38", 27 | "redbud": "1.5.0", 28 | "lerna": "6.4.1", 29 | "react": "18.2.0", 30 | "react-dom": "18.2.0", 31 | "typescript": "4.9.5" 32 | }, 33 | "config": { 34 | "commitizen": { 35 | "path": "cz-conventional-changelog" 36 | } 37 | }, 38 | "workspaces": [ 39 | "packages/*" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /packages/aliplayer/.redbudrc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'redbud'; 2 | 3 | export default defineConfig({ 4 | extends: '../../.redbudrc.base.ts' 5 | }); 6 | -------------------------------------------------------------------------------- /packages/aliplayer/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [2.1.1](https://github.com/pansyjs/react-components/compare/@pansy/react-aliplayer@2.1.0...@pansy/react-aliplayer@2.1.1) (2023-03-02) 7 | 8 | 9 | ### Bug Fixes 10 | 11 | * 修复播放器绑定事件逻辑错误 ([d7c0112](https://github.com/pansyjs/react-components/commit/d7c01126d8964dd851d0ca11ce12c9790080569a)) 12 | * 修复偶发 aliplayer 报 没有为播放器指定容器 ([56eab0c](https://github.com/pansyjs/react-components/commit/56eab0c0a1ed4d2f5d30f75f65af3bee0e8faa25)) 13 | 14 | 15 | 16 | 17 | 18 | # [2.1.0](https://github.com/pansyjs/react-components/compare/@pansy/react-aliplayer@1.3.0...@pansy/react-aliplayer@2.1.0) (2023-02-02) 19 | 20 | 21 | ### Features 22 | 23 | * **aliplayer:** 代码重构 ([cc512af](https://github.com/pansyjs/react-components/commit/cc512af63644dc1baf1622dd6ad9766465ca5eb9)) 24 | 25 | 26 | 27 | 28 | 29 | # [1.3.0](https://github.com/pansyjs/react-components/compare/@pansy/react-aliplayer@1.2.1...@pansy/react-aliplayer@1.3.0) (2021-07-13) 30 | 31 | 32 | ### Features 33 | 34 | * **react-aliplayer:** update default version ([873bec5](https://github.com/pansyjs/react-components/commit/873bec5c19bc60032f248c3c9377625e11bedc8a)) 35 | 36 | 37 | 38 | 39 | 40 | ## [1.2.1](https://github.com/pansyjs/react-components/compare/@pansy/react-aliplayer@1.2.0...@pansy/react-aliplayer@1.2.1) (2021-07-13) 41 | 42 | 43 | ### Bug Fixes 44 | 45 | * **aliplayer:** 修复乾坤使用问题 ([eadd047](https://github.com/pansyjs/react-components/commit/eadd047ff98e7cdd67d4089ce85e26a25cd71325)) 46 | 47 | 48 | 49 | 50 | 51 | # [1.2.0](https://github.com/pansyjs/react-components/compare/@pansy/react-aliplayer@1.1.0...@pansy/react-aliplayer@1.2.0) (2021-07-12) 52 | 53 | 54 | ### Features 55 | 56 | * **aliplayer:** 支持乾坤沙盒 ([dd0c55a](https://github.com/pansyjs/react-components/commit/dd0c55afe7c5662dfc36d203adf667647ab98c4c)) 57 | 58 | 59 | 60 | 61 | 62 | # 1.1.0 (2021-04-13) 63 | 64 | 65 | ### Features 66 | 67 | * **aliplayer:** update aliplayer sdk version ([c1a8fe2](https://github.com/pansyjs/react-components/commit/c1a8fe2adae92f146df7151abab0a84d7b64b3b1)) 68 | * **aliplayer:** 添加flv配置类型定义 ([b3e93fa](https://github.com/pansyjs/react-components/commit/b3e93fa12c0d450f503786baca4d414233939f9f)) 69 | * **amap:** add InfoWindow component ([db0e9ae](https://github.com/pansyjs/react-components/commit/db0e9ae551b7d08b21b312f2534c2fa3a232b2f5)) 70 | -------------------------------------------------------------------------------- /packages/aliplayer/README.md: -------------------------------------------------------------------------------- 1 |

React AliPlayer

2 | 3 | React component wrapper for aliplayer. 4 | 5 | ## 🏗 安装 6 | 7 | ```sh 8 | # npm 安装 9 | npm install --save @pansy/react-aliplayer 10 | 11 | # yarn 安装 12 | yarn add @pansy/react-aliplayer 13 | ``` 14 | 15 | ## 🔨 使用 16 | 17 | ```tsx 18 | import React, { FC } from 'react'; 19 | import Player from '@pansy/react-aliplayer'; 20 | 21 | const Example: FC = () => { 22 | return ( 23 |
24 | 27 |
28 | ); 29 | }; 30 | 31 | export default Example; 32 | ``` 33 | -------------------------------------------------------------------------------- /packages/aliplayer/docs/aliplayer.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: AliPlayer 阿里播放器 3 | nav: 4 | title: 组件 5 | path: /components 6 | order: 2 7 | group: 8 | path: /video 9 | title: '音视频组件' 10 | --- 11 | 12 | # AliPlayer 阿里播放器 13 | 14 | ## 何时使用 15 | 16 | - 播放视频。 17 | - 播放直播流(flv、hls)。 18 | 19 | ## 代码示例 20 | 21 | ### 简单使用 22 | 23 | 24 | 25 | ### 隐藏控制栏 26 | 27 | 28 | 29 | ### 直播 30 | 31 | 32 | 33 | ### 手动控制 34 | 35 | 36 | 37 | ## API 38 | 39 | | 参数 | 说明 | 类型 | 默认值 | 40 | | --------- | ------------------ | ------- | ------------------ | 41 | | className | 额外的样式类 | string | -- | 42 | | style | 额外的样式 | CSSProperties | -- | 43 | | source | 播放源 | string | -- | 44 | | isLive | 是否是直播 | boolean | `false` | 45 | | options | 配置参数 | object | {} | 46 | | version | 阿里播放器版本 | string | 2.8.2 | 47 | |changeSourceMode|修改播放源处理模式 loadByUrl(加载播放源)、init(销毁重新创建实例) |`loadByUrl` \| `init`| `init` | 48 | | hideControlbar | 是否隐藏控制栏 | boolean | false | 49 | | cssLinkTemplate | 阿里播放器CSS模板 | string | //g.alicdn.com/de/prismplayer/{%version}/skins/default/aliplayer-min.css | 50 | | scriptSrcTemplate | 阿里播放器JS模板 | string | //g.alicdn.com/de/prismplayer/{%version}/aliplayer-min.js | 51 | | loading | 用于在播放器加载成功前渲染 | ReactNode | -- | 52 | | onReady | 播放器视频初始化按钮渲染完毕 | Function | -- | 53 | | onPlay | 视频暂停时触发 | Function | -- | 54 | | onPause | 播放器视频初始化按钮渲染完毕 | Function | -- | 55 | | onCanplay | 能够开始播放音频/视频时发生,会多次触发,仅H5播放器 | Function | -- | 56 | | onPlaying | 播放中,会触发多次 | Function | -- | 57 | | onLiveStreamStop | 直播流中断时触发 | Function | -- | 58 | | onM3u8Retry | m3u8直播流中断后重试事件 | Function | -- | 59 | | onHideBar | 控制栏自动隐藏事件 | Function | -- | 60 | | onShowBar | 控制栏自动显示事件 | Function | -- | 61 | | onWaiting | 数据缓冲事件 | Function | -- | 62 | | onSnapshoted | 截图完成事件 | Function | -- | 63 | | onRequestFullScreen | 全屏事件,仅H5支持 | Function | -- | 64 | | onCancelFullScreen | 取消全屏事件,iOS下不会触发,仅H5支持 | Function | -- | 65 | | onError | 错误事件 | Function | -- | 66 | 67 | **注意** 68 | 69 | cssLinkTemplate、scriptSrcTemplate 中的 `{%version}` 会使用version进行替换 70 | 71 | ## options 72 | 73 | | 参数 | 说明 | 类型 | 默认值 | 74 | | --------- | ------------------ | ------- | ------------------ | 75 | | vid | 媒体转码服务的媒体Id | string | -- | 76 | | playauth | 播放权证 | string | -- | 77 | | height | 播放器高度 | string | `100%` | 78 | | width | 播放器宽度 | string | `100%` | 79 | | videoWidth | 视频宽度,仅h5支持 | string | -- | 80 | | videoHeight | 视频高度,仅h5支持 | string | -- | 81 | | preload | 播放器自动加载,目前仅h5可用 | boolean | -- | 82 | | cover | 播放器默认封面图片 | string | -- | 83 | | autoplay | 播放器是否自动播放 | boolean | -- | 84 | | rePlay | 播放器自动循环播放 | boolean | -- | 85 | | useH5Prism | 指定使用H5播放器 | boolean | -- | 86 | | useFlashPrism | 指定使用Flash播放器 | boolean | -- | 87 | | playsinline | H5是否内置播放,有的Android浏览器不起作用 | boolean | -- | 88 | | showBuffer | 显示播放时缓冲图标 | boolean | -- | 89 | | skinRes | 皮肤图片,不建议随意修改该字段,如要修改,请参照皮肤定制 | any | -- | 90 | | skinLayout | 功能组件布局配置,不传该字段使用默认布局。传false隐藏所有功能组件,请参照皮肤定制 | any | -- | 91 | | controlBarVisibility | 控制面板的实现 | 'click' \| 'hover' \| 'always' | -- | 92 | | showBarTime | 控制栏自动隐藏时间 | number | -- | 93 | | extraInfo | JSON串用于定制性接口参数 | string | -- | 94 | | enableSystemMenu | 是否允许系统右键菜单显示 | boolean | -- | 95 | | format | 指定播放地址格式,只有使用vid的播放方式时支持 | 'mp4'\| 'm3u8' \| 'flv' \| 'mp3' | -- | 96 | | mediaType | 指定返回音频还是视频,只有使用vid的播放方式时支持 | 'video' \| 'audio' | -- | 97 | | qualitySort | 指定排序方式,只有使用vid + plauth播放方式时支持 | 'desc' \| 'asc' | -- | 98 | | definition | 显示视频清晰度,多个用逗号分隔 | string | -- | 99 | | defaultDefinition | 默认视频清晰度 |'FD' \| 'LD' \| 'SD' \| 'HD' \| 'OD' \| '2K' \| '4K' | -- | 100 | | x5_type | 声明启用同层H5播放器,启用时设置的值为‘h5’ | string | `auto` | 101 | | x5_fullscreen | 声明视频播放时是否进入到TBS的全屏模式 | boolean | false | 102 | | x5_video_position | 声明视频播在界面上的位置 | 'top' \| 'center' | `center` | 103 | | x5_orientation | 声明视频播放时是否进入到TBS的全屏模式 | 'landscape' \| 'landscape' | -- | 104 | | x5LandscapeAsFullScreen | 声明TBS全屏播放是否横屏 | boolean | -- | 105 | | autoPlayDelay | 延迟播放时间,单位为秒 | number | -- | 106 | | autoPlayDelayDisplayText | 延迟播放提示文本 | string | -- | 107 | | language | 指定语言 | string | -- | 108 | | languageTexts | 国际化 | string | -- | 109 | | snapshot | flash启用截图功能 | boolean | -- | 110 | | snapshotWatermark | H5设置截图水印 | object | -- | 111 | | useHlsPluginForSafari | Safari浏览器可以启用Hls插件播放,Safari 11除外。 | boolean | -- | 112 | | enableStashBufferForFlv | H5播放flv时,设置是否启用播放缓存,只在直播下起作用 | boolean | -- | 113 | | stashInitialSizeForFlv | H5播放flv时,初始缓存大小,只在直播下起作用 | number | -- | 114 | | loadDataTimeout | 缓冲多长时间后,提示用户切换低清晰度 | number | 20 | 115 | | waitingTimeout | 大缓冲超时时间 | number | 60 | 116 | | liveStartTime | 直播开始时间,直播时移功能使用,格式为:“2018/01/04 12:00:00” | string | -- | 117 | | liveOverTime | 直播结束时间,直播时移功能使用,格式为:“2018/01/04 12:00:00” | string | -- | 118 | | liveTimeShiftUrl | 直播可用时移查询地址 | string | -- | 119 | | liveShiftSource | flv直播地址播放时,hls的流地址 | string | -- | 120 | | recreatePlayer | flv直播和hls时移切换是,重新创建播放器方法 | function | -- | 121 | | diagnosisButtonVisible | 是否显示检测按钮 | boolean | true | 122 | | disableSeek | 禁用进度条的Seek,默认为false,仅Flash支持 | boolean | false | 123 | | encryptType | 加密类型 | 0 /| 1 | 0 | 124 | | progressMarkers | 进度条打点内容数组 | object[] | -- | 125 | | vodRetry | 点播失败重试次数 | number | 3 | 126 | | liveRetry | 直播播放失败重试次数 | number | 5 | 127 | 128 | ## 错误代码 129 | 130 | |代码|含义| 131 | |--|--| 132 | |4001|参数不合理| 133 | |4002|鉴权过期| 134 | |4003|无效地址| 135 | |4004|地址不存在| 136 | |4005|开始下载数据错误,检测网络情况或播放地址是否可以访问。| 137 | |4006|开始下载元数据数据错误| 138 | |4007|播放时错误| 139 | |4008|加载超时,检测网络情况或播放地址是否可以访问| 140 | |4009|请求数据错误,测网络情况或播放地址是否可以访问| 141 | |4010|不支持加密视频播放| 142 | |4011|播放格式不支持| 143 | |4012|playauth解析错误| 144 | |4013|播放数据解码错误MEDIA_ERR_DECODE,检测浏览器是否支持视频格式。| 145 | |4014|网络不可用| 146 | |4015|获取数据过程被中止MEDIA_ERR_ABORTED| 147 | |4016|播网络错误加载数据失败MEDIA_ERR_NETWORK| 148 | |4017|返回的播放地址为空| 149 | |4400|未知错误MEDIA_ERR_SRC_NOT_SUPPORTED(由于服务器或网络原因不能加载资源,或者格式不支持)| 150 | |4500|服务端请求错误,查看Network里点播服务的请求的具体错误| 151 | -------------------------------------------------------------------------------- /packages/aliplayer/docs/demos/demo-01.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Player } from '@pansy/react-aliplayer'; 3 | 4 | export default () => { 5 | return ( 6 | 15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /packages/aliplayer/docs/demos/demo-02.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Player } from '@pansy/react-aliplayer'; 3 | 4 | export default () => { 5 | return ( 6 |
7 | 15 |
16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /packages/aliplayer/docs/demos/demo-03.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Player } from '@pansy/react-aliplayer'; 3 | 4 | export default () => { 5 | return ( 6 |
7 | 15 |
16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /packages/aliplayer/docs/demos/demo-04.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react'; 2 | import { Button, Space } from 'antd' 3 | import { Player } from '@pansy/react-aliplayer'; 4 | 5 | import type { PlayerRef } from '@pansy/react-aliplayer'; 6 | 7 | export default () => { 8 | const playerRef = useRef(null); 9 | 10 | const handlePlay = () => { 11 | if (!playerRef.current) return; 12 | const player = playerRef.current.getInstance(); 13 | player && player.play() 14 | } 15 | 16 | const handlePause = () => { 17 | if (!playerRef.current) return; 18 | const player = playerRef.current.getInstance(); 19 | player && player.pause() 20 | } 21 | 22 | const handleReplay = () => { 23 | if (!playerRef.current) return; 24 | const player = playerRef.current.getInstance(); 25 | player && player.replay() 26 | } 27 | 28 | const handleSeek = () => { 29 | if (!playerRef.current) return; 30 | const player = playerRef.current.getInstance(); 31 | player && player.seek(10) 32 | } 33 | 34 | const handleLoadByUrl = () => { 35 | if (!playerRef.current) return; 36 | const player = playerRef.current.getInstance(); 37 | player && player.loadByUrl('//stream7.iqilu.com/10339/upload_transcode/202002/18/202002181038474liyNnnSzz.mp4') 38 | } 39 | 40 | return ( 41 | <> 42 | 48 | 49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | ); 60 | }; 61 | -------------------------------------------------------------------------------- /packages/aliplayer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pansy/react-aliplayer", 3 | "version": "2.1.1", 4 | "main": "lib/index.js", 5 | "module": "es/index.js", 6 | "types": "lib/index.d.ts", 7 | "files": [ 8 | "es", 9 | "lib" 10 | ], 11 | "scripts": { 12 | "build": "redbud build" 13 | }, 14 | "license": "MIT", 15 | "peerDependencies": { 16 | "react": ">=16.9.0", 17 | "react-dom": ">=16.9.0" 18 | }, 19 | "browserslist": [ 20 | "last 2 versions", 21 | "Firefox ESR", 22 | "> 1%", 23 | "ie >= 11" 24 | ], 25 | "publishConfig": { 26 | "registry": "https://registry.npmjs.org", 27 | "access": "public" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/aliplayer/src/index.less: -------------------------------------------------------------------------------- 1 | @player-prefix-cls: ~'pansy-aliplayer'; 2 | 3 | .@{player-prefix-cls} { 4 | 5 | &.hide-controlbar.prism-player { 6 | .prism-controlbar { 7 | display: none !important; 8 | } 9 | } 10 | 11 | .prism-info-left-bottom { 12 | display: none !important; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/aliplayer/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { Player } from './player'; 2 | 3 | export type { PlayerErrorEvent, PlayerConfig, PlayerProps, PlayerRef } from './types'; 4 | 5 | export { Player }; 6 | export default Player; 7 | -------------------------------------------------------------------------------- /packages/aliplayer/src/player.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useState, useEffect, useRef, useImperativeHandle } from 'react' 3 | 4 | import type { PlayerProps, PlayerConfig, PlayerInstance, PlayerRef } from './types'; 5 | 6 | const playerScriptId = 'ali-player-js'; 7 | const playerLinkId = 'ali-player-css'; 8 | const tempPrefix = '//g.alicdn.com/de/prismplayer/{%version}/'; 9 | const eventNames = [ 10 | 'ready', 11 | 'play', 12 | 'pause', 13 | 'canplay', 14 | 'playing', 15 | 'ended', 16 | 'hideBar', 17 | 'showBar', 18 | 'waiting', 19 | 'snapshoted', 20 | 'timeupdate', 21 | 'onM3u8Retry', 22 | 'liveStreamStop', 23 | 'cancelFullScreen', 24 | 'requestFullScreen', 25 | 'error', 26 | ]; 27 | 28 | const InternalPlayer: React.ForwardRefRenderFunction = (props, ref) => { 29 | const { 30 | prefixCls = 'pansy-aliplayer', 31 | className, 32 | style, 33 | hideControlbar = false, 34 | options = {}, 35 | loading, 36 | source, 37 | isLive, 38 | version = '2.13.4', 39 | // version = '2.9.22', 40 | cssLinkTemplate = `${tempPrefix}skins/default/aliplayer-min.css`, 41 | scriptSrcTemplate = `${tempPrefix}aliplayer-min.js`, 42 | } = props; 43 | const playerInstance = useRef(); 44 | const playerId = useRef(`aliplayer-${Math.floor(Math.random() * 1000000)}`); 45 | const [isLoading, setIsLoading] = useState(true); 46 | 47 | useEffect( 48 | () => { 49 | insertLinkTag(); 50 | init(); 51 | 52 | return () => { 53 | playerInstance.current && playerInstance.current.dispose(); 54 | } 55 | }, 56 | [] 57 | ) 58 | 59 | useEffect( 60 | () => { 61 | if (playerInstance.current && props.source) { 62 | playerInstance.current.loadByUrl(props.source); 63 | } 64 | }, 65 | [props.source, playerInstance.current] 66 | ) 67 | 68 | useImperativeHandle( 69 | ref, 70 | () => { 71 | return { 72 | getInstance: () => playerInstance.current! 73 | } 74 | }, 75 | [playerInstance.current] 76 | ) 77 | 78 | const classes = [ 79 | prefixCls, 80 | className, 81 | 'prism-player', 82 | hideControlbar ? 'hide-controlbar' : null 83 | ].filter(item => item).join(' ').trim(); 84 | 85 | const getPath = (path: string, version: string): string => { 86 | return path.replace(/\{([^{]*?)%version(.*?)\}/g, version.toString()); 87 | } 88 | 89 | const insertLinkTag = () => { 90 | const playerLinkTag = document.getElementById(playerLinkId); 91 | 92 | if (!playerLinkTag && version && cssLinkTemplate) { 93 | const link = document.createElement('link'); 94 | link.type = 'text/css'; 95 | link.rel = 'stylesheet'; 96 | link.href = getPath(cssLinkTemplate, version); 97 | link.id = playerLinkId; 98 | document.head.appendChild(link); 99 | } 100 | } 101 | 102 | const insertScriptTag = () => { 103 | let playerScriptTag = document.getElementById(playerScriptId) as HTMLScriptElement; 104 | 105 | if (!playerScriptTag) { 106 | playerScriptTag = document.createElement('script'); 107 | playerScriptTag.type = 'text/javascript'; 108 | playerScriptTag.charset = 'utf-8'; 109 | playerScriptTag.src = getPath(scriptSrcTemplate, version); 110 | playerScriptTag.id = playerScriptId; 111 | 112 | document.body.appendChild(playerScriptTag); 113 | } 114 | 115 | playerScriptTag.addEventListener('load', () => { 116 | setIsLoading(false); 117 | initAliPlayer(); 118 | }); 119 | } 120 | 121 | const init = () => { 122 | if (window['Aliplayer']) { 123 | setIsLoading(false); 124 | initAliPlayer(); 125 | } else { 126 | insertScriptTag(); 127 | } 128 | } 129 | 130 | const initAliPlayer = () => { 131 | const config: Partial = { 132 | ...options, 133 | useH5Prism: true, 134 | id: playerId.current, 135 | source, 136 | isLive: !!isLive 137 | }; 138 | 139 | if (!config.width) { 140 | config.width = '100%'; 141 | } 142 | 143 | if (!config.height) { 144 | config.height = '100%'; 145 | } 146 | 147 | if (config.autoplay === undefined) { 148 | config.autoplay = false; 149 | } 150 | 151 | if (playerInstance.current) { 152 | playerInstance.current.dispose(); 153 | } 154 | 155 | const Aliplayer = window['Aliplayer'] || window['proxy']?.Aliplayer; 156 | 157 | playerInstance.current = new Aliplayer(config); 158 | playerInstance.current!.setVolume(0); 159 | initEvents(playerInstance.current); 160 | } 161 | 162 | const initEvents = (player?: PlayerInstance) => { 163 | if (!player) return; 164 | eventNames.forEach((eventName) => { 165 | let propsEventName = eventName; 166 | if (!eventName.startsWith('on')) { 167 | propsEventName = `on${eventName.charAt(0).toUpperCase()}${eventName.slice(1, eventName.length)}` 168 | } 169 | 170 | if (propsEventName in props && props[propsEventName]) { 171 | player.on(eventName, props[propsEventName]); 172 | } 173 | }) 174 | } 175 | 176 | return ( 177 |
178 | {isLoading ? loading : null} 179 |
180 | ) 181 | } 182 | 183 | export const Player = React.forwardRef(InternalPlayer); 184 | -------------------------------------------------------------------------------- /packages/aliplayer/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface PlayerErrorEvent { 2 | target: HTMLDivElement; 3 | type: 'error'; 4 | paramData: { 5 | display_msg: string; 6 | error_code: number; 7 | } 8 | } 9 | 10 | export interface PlayerConfig { 11 | // 播放器外层容器的dom元素id 12 | id: string; 13 | // 视频播放地址url 14 | source: string; 15 | // 媒体转码服务的媒体Id 16 | vid: string; 17 | // 播放权证 18 | playauth: string; 19 | // 播放器高度,可形如‘100%’或者‘100px’ 20 | height: string; 21 | // 播放器宽度,可形如‘100%’或者‘100px’ 22 | width: string; 23 | // 视频宽度,仅h5支持 24 | videoWidth: string; 25 | // 视频高度,仅h5支持 26 | videoHeight: string; 27 | // 播放器自动加载,目前仅h5可用 28 | preload: boolean; 29 | // 播放器默认封面图片 30 | cover: string; 31 | // 播放内容是否为直播 32 | isLive: boolean; 33 | // flv配置 34 | flvOption: { 35 | hasAudio: boolean; 36 | } 37 | // 播放器是否自动播放 38 | autoplay: boolean; 39 | // 播放器自动循环播放 40 | rePlay: boolean; 41 | // 指定使用H5播放器 42 | useH5Prism: boolean; 43 | // 指定使用Flash播放器 44 | useFlashPrism: boolean; 45 | // H5是否内置播放,有的Android浏览器不起作用 46 | playsinline: boolean; 47 | // 显示播放时缓冲图标 48 | showBuffer: boolean; 49 | // 皮肤图片,不建议随意修改该字段,如要修改,请参照皮肤定制 50 | skinRes: any; 51 | // 功能组件布局配置,不传该字段使用默认布局。传false隐藏所有功能组件,请参照皮肤定制。 52 | skinLayout: any[] | boolean; 53 | // 控制面板的实现 54 | controlBarVisibility: 'click' | 'hover' | 'always'; 55 | // 控制栏自动隐藏时间 56 | showBarTime: number; 57 | // JSON串用于定制性接口参数 58 | extraInfo: string; 59 | // 是否允许系统右键菜单显示 60 | enableSystemMenu: boolean; 61 | // 指定播放地址格式,只有使用vid的播放方式时支持 62 | format: 'mp4'| 'm3u8' | 'flv' | 'mp3'; 63 | // 指定返回音频还是视频,只有使用vid的播放方式时支持。 64 | mediaType: 'video' | 'audio'; 65 | // 指定排序方式,只有使用vid + plauth播放方式时支持。 66 | qualitySort: 'desc' | 'asc'; 67 | // 显示视频清晰度,多个用逗号分隔 68 | // 取值范围:FD(流畅)LD(标清)SD(高清)HD(超清)OD(原画)2K(2K)4K(4K),仅H5支持。 69 | definition: string; 70 | // 默认视频清晰度,此值是vid对应流的一个清晰度 71 | defaultDefinition: 'FD' | 'LD' | 'SD' | 'HD' | 'OD' | '2K' | '4K'; 72 | // 声明启用同层H5播放器,启用时设置的值为‘h5’ 73 | x5_type: string; 74 | // 声明视频播放时是否进入到TBS的全屏模式 75 | x5_fullscreen: boolean; 76 | // 声明视频播在界面上的位置 77 | x5_video_position: 'top' | 'center'; 78 | // 声明TBS播放器支持的方向 79 | x5_orientation: 'landscape' | 'landscape'; 80 | // 声明TBS全屏播放是否横屏 81 | x5LandscapeAsFullScreen: boolean; 82 | // 延迟播放时间,单位为秒。 83 | autoPlayDelay: number; 84 | // 延迟播放提示文本 85 | autoPlayDelayDisplayText: string; 86 | // 国际化 87 | language: string; 88 | languageTexts: string; 89 | // flash启用截图功能 90 | snapshot: boolean; 91 | // H5设置截图水印 92 | snapshotWatermark: object; 93 | // Safari浏览器可以启用Hls插件播放,Safari 11除外。 94 | useHlsPluginForSafari: boolean; 95 | // H5播放flv时,设置是否启用播放缓存,只在直播下起作用 96 | enableStashBufferForFlv: boolean; 97 | // H5播放flv时,初始缓存大小,只在直播下起作用 98 | stashInitialSizeForFlv: number; 99 | // 缓冲多长时间后,提示用户切换低清晰度,默认:20秒 100 | loadDataTimeout: number; 101 | // 最大缓冲超时时间,超过这个时间会有错误提示,默认:60秒 102 | waitingTimeout: number; 103 | // 直播开始时间,直播时移功能使用,格式为:“2018/01/04 12:00:00” 104 | liveStartTime: string; 105 | // 直播结束时间,直播时移功能使用,格式为:“2018/01/04 12:00:00” 106 | liveOverTime: string; 107 | // 直播可用时移查询地址 108 | liveTimeShiftUrl: string; 109 | // flv直播地址播放时,hls的流地址 110 | liveShiftSource: string; 111 | // flv直播和hls时移切换是,重新创建播放器方法 112 | recreatePlayer: Function; 113 | // 是否显示检测按钮,默认为true 114 | diagnosisButtonVisible: boolean; 115 | // 禁用进度条的Seek,默认为false,仅Flash支持 116 | disableSeek: boolean; 117 | // 加密类型,播放点播私有加密视频时,设置值为1,默认值为0 118 | encryptType: 0 | 1; 119 | // 进度条打点内容数组 120 | progressMarkers: { 121 | offset: number; 122 | text: string; 123 | isCustomized: boolean; 124 | }[]; 125 | // 点播失败重试次数,默认3次 126 | vodRetry: number; 127 | // 直播播放失败重试次数,默认5次 128 | liveRetry: number; 129 | } 130 | 131 | export type PlayerStatus = 132 | 'init' | 133 | 'ready' | 134 | 'loading' | 135 | 'play' | 136 | 'pause' | 137 | 'playing' | 138 | 'waiting' | 139 | 'error' | 140 | 'ended'; 141 | 142 | export interface PlayerProps { 143 | /** 样式类前缀 */ 144 | prefixCls?: string; 145 | /** 自定义样式类 */ 146 | className?: string; 147 | /** 自定义样式 */ 148 | style?: React.CSSProperties; 149 | // 播放源 150 | source: string; 151 | // 是否是直播 152 | isLive?: boolean; 153 | // 用于在播放器加载成功前渲染 154 | loading?: React.ReactNode; 155 | /** 播放器配置 */ 156 | options?: Partial>; 157 | // aliplayer版本 158 | version?: string; 159 | // 是否隐藏控制栏 160 | hideControlbar?: boolean; 161 | changeSourceMode?: 'loadByUrl' | 'init', 162 | // 播放器CSS模板 163 | cssLinkTemplate?: string; 164 | // 播放器JS模板 165 | scriptSrcTemplate?: string; 166 | // 播放器视频初始化按钮渲染完毕 167 | onReady?: () => void; 168 | // 视频由暂停恢复为播放时触发 169 | onPlay?: () => void; 170 | // 视频暂停时触发 171 | onPause?: () => void; 172 | // 能够开始播放音频/视频时发生,会多次触发,仅H5播放器 173 | onCanplay?: () => void; 174 | // 播放中,会触发多次 175 | onPlaying?: () => void; 176 | // 当前视频播放完毕时触发 177 | onEnded?: () => void; 178 | // 直播流中断时触发 179 | onLiveStreamStop?: () => void; 180 | // m3u8直播流中断后重试事件 181 | onM3u8Retry?: () => void; 182 | // 控制栏自动隐藏事件 183 | onHideBar?: () => void; 184 | // 控制栏自动显示事件 185 | onShowBar?: () => void; 186 | // 数据缓冲事件 187 | onWaiting?: () => void; 188 | // 截图完成事件 189 | onSnapshoted?: () => void; 190 | // 播放位置发生改变时触发,仅H5播放器。 191 | // 可通过getCurrentTime方法,得到当前播放时间。 192 | onTimeupdate?: () => void; 193 | // 全屏事件,仅H5支持 194 | onRequestFullScreen?: () => void; 195 | // 取消全屏事件,iOS下不会触发,仅H5支持 196 | onCancelFullScreen?: () => void; 197 | // 错误事件 198 | onError?: (e: PlayerErrorEvent) => void; 199 | } 200 | 201 | export interface PlayerRef { 202 | getInstance: () => PlayerInstance; 203 | } 204 | 205 | export interface PlayerInstance { 206 | /** 播放视频 */ 207 | play: () => void; 208 | /** 暂停视频 */ 209 | pause: () => void; 210 | /** 重播视频 */ 211 | replay: () => void; 212 | /** 跳转到某个已加载的时刻进行播放,时间单位:秒 */ 213 | seek: (time: number) => void; 214 | /** 获取当前的播放时刻,返回的时间单位:秒 */ 215 | getCurrentTime: () => number; 216 | /** 获取视频总时长,返回的时间单位:秒 */ 217 | getDuration: () => number; 218 | /** 获取当前的音量 */ 219 | getVolume: () => number; 220 | /** 设置当前的音量 */ 221 | setVolume: (vol: number) => void; 222 | /** 设置当前的音量 */ 223 | loadByUrl: (url: string, time?: number) => void; 224 | /** 设置播放器大小 */ 225 | setPlayerSize: (w: number, h: number) => void; 226 | /** 手动设置播放的倍速,支持0.5~2倍速播放 */ 227 | setSpeed: (speed: number) => void; 228 | /** 设置截图参数 */ 229 | setSanpshotProperties: (w: number, h: number, rate: number) => void; 230 | fullscreenService: { 231 | /** 播放器全屏 */ 232 | requestFullScreen: () => void; 233 | /** 播放器退出全屏 */ 234 | cancelFullScreen: () => void; 235 | /** 获取播放器全屏状态 */ 236 | getIsFullScreen: () => boolean; 237 | }, 238 | /** 获取播放器状态 */ 239 | getStatus: () => PlayerStatus; 240 | /** 设置旋转角度 */ 241 | setRotate: (rotate: number) => void; 242 | /** 获取旋转角度 */ 243 | getRotate: () => number; 244 | /** 设置镜像 */ 245 | setImage: (image: 'horizon' | 'vertical') => number; 246 | /** 播放器销毁 */ 247 | dispose: () => void; 248 | /** 设置封面 */ 249 | setCover: (cover: string) => void; 250 | /** 设置打点数据 */ 251 | setProgressMarkers: (markers: any) => void; 252 | /** 设置试看时间 */ 253 | setPreviewTime: (time: number) => void; 254 | /** 获取试看时间 */ 255 | getPreviewTime: () => number; 256 | /** 是否试看 */ 257 | isPreview: () => boolean; 258 | /** 绑定事件 */ 259 | on: (eventName: string, callback: any) => void; 260 | replayByVidAndPlayAuth: (vid: string, playAuth: string) => void; 261 | } 262 | -------------------------------------------------------------------------------- /packages/aliplayer/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css'; 2 | declare module '*.less'; 3 | 4 | interface Window { 5 | Aliplayer: any; 6 | globalThis: Window; 7 | } 8 | -------------------------------------------------------------------------------- /packages/responsive-card/.redbudrc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'redbud'; 2 | 3 | export default defineConfig({ 4 | extends: '../../.redbudrc.base.ts' 5 | }); 6 | -------------------------------------------------------------------------------- /packages/responsive-card/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [0.2.1](https://github.com/pansyjs/react-components/compare/@pansy/react-responsive-card@0.2.0...@pansy/react-responsive-card@0.2.1) (2023-02-02) 7 | 8 | **Note:** Version bump only for package @pansy/react-responsive-card 9 | 10 | 11 | 12 | 13 | 14 | # [0.2.0](https://github.com/pansyjs/react-components/compare/@pansy/react-responsive-card@0.1.2...@pansy/react-responsive-card@0.2.0) (2022-02-17) 15 | 16 | 17 | ### Features 18 | 19 | * **responsive-card:** 代码优化 ([6d40fd6](https://github.com/pansyjs/react-components/commit/6d40fd63756f66add6070649fcb53da9260b306b)) 20 | 21 | 22 | 23 | 24 | 25 | ## [0.1.2](https://github.com/pansyjs/react-components/compare/@pansy/react-responsive-card@0.1.1...@pansy/react-responsive-card@0.1.2) (2022-01-24) 26 | 27 | **Note:** Version bump only for package @pansy/react-responsive-card 28 | 29 | 30 | 31 | 32 | 33 | ## 0.1.1 (2022-01-24) 34 | 35 | **Note:** Version bump only for package @pansy/react-responsive-card 36 | -------------------------------------------------------------------------------- /packages/responsive-card/README.md: -------------------------------------------------------------------------------- 1 |

@pansy/react-responsive-card

2 | 3 | 卡片自适应间距 4 | 5 | ## 🏗 安装 6 | 7 | ```sh 8 | # npm 安装 9 | npm install --save @pansy/react-responsive-card 10 | 11 | # yarn 安装 12 | yarn add @pansy/react-responsive-card 13 | 14 | # pnpm 安装 15 | pnpm install @pansy/react-responsive-card 16 | ``` 17 | 18 | ## 🔨 使用 19 | 20 | ```tsx 21 | import React from 'react'; 22 | import { CheckCard } from '@ant-design/pro-card'; 23 | import { ResponsiveCard } from '@pansy/react-responsive-card'; 24 | 25 | export default () => { 26 | return ( 27 | 28 | 29 | 35 | 41 | 47 | 48 | 49 | ); 50 | }; 51 | ``` 52 | -------------------------------------------------------------------------------- /packages/responsive-card/demo/demo01.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { CheckCard } from '@ant-design/pro-card'; 3 | import { ResponsiveCard } from '@pansy/react-responsive-card'; 4 | 5 | const options = [ 6 | { title: '🍊 Orange', description: '🍊 Orange', value: 'option1' }, 7 | { title: '🍐 Pear', description: '🍐 Pear', value: 'option2' }, 8 | { title: '🍎 Apple', description: '🍎 Apple', value: 'option3' }, 9 | { title: '🍎 Apple1', description: '🍎 Apple1', value: 'option4' }, 10 | ] 11 | 12 | export default () => { 13 | return ( 14 | 15 | 16 | {(config) => { 17 | return options.map((item, index) => { 18 | const style = { 19 | width: config.width, 20 | marginRight: 21 | (index + 1) % config.span != 0 22 | ? config.gutter 23 | : 0, 24 | } 25 | 26 | return ( 27 | 32 | ) 33 | }) 34 | }} 35 | 36 | 37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /packages/responsive-card/demo/demo02.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { CheckCard } from '@ant-design/pro-card'; 3 | import { ResponsiveCard } from '@pansy/react-responsive-card'; 4 | 5 | export default () => { 6 | return ( 7 | 8 | 9 | 15 | 21 | 27 | 28 | 29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /packages/responsive-card/demo/demo03.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { CheckCard } from '@ant-design/pro-card'; 3 | import { ResponsiveCard } from '@pansy/react-responsive-card'; 4 | 5 | export default () => { 6 | return ( 7 | 8 | 9 | 15 | 21 | 27 | 28 | 29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /packages/responsive-card/docs/responsive-card.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ResponsiveCard 自适应间距卡片 3 | nav: 4 | title: 组件 5 | path: /components 6 | order: 1 7 | group: 8 | path: /basic 9 | title: '基础组件' 10 | order: 0 11 | --- 12 | 13 | # ResponsiveCard 卡片自适应间距 14 | 15 | ## 何时使用 16 | 17 | - 需要将卡片进行自适应间距时 18 | 19 | ## 代码演示 20 | 21 | ### 基础示例 22 | 23 | 24 | 25 | ### 自定义渲染 26 | 27 | 28 | 29 | 30 | ### 自定义间距 31 | 32 | 33 | 34 | ## API 35 | 36 | | 参数 | 说明 | 类型 | 默认值 | 版本 | 37 | | ------------ | --------------| ------------------- | ------ | ---- | 38 | | className | 额外的样式类 | `string` | -- | -- | 39 | | style | 额外的样式 | `React.CSSProperties` | -- | -- | 40 | | defaultWidth | 卡片的默认宽度 | `number` | `260` | -- | 41 | | gutter | 分屏的间隔 | `number` \| `[number, number]` | `16` | -- | 42 | | children | 需要展示的业务内容 | `((config: AdaptiveConfig) => React.ReactNode) | React.ReactNode` |-- | -- | 43 | 44 | 45 | ```ts 46 | interface AdaptiveConfig { 47 | /** 每项的宽度 */ 48 | width: number; 49 | /** 左右间距 */ 50 | gutter: number; 51 | /** 每行的数目 */ 52 | span: number; 53 | } 54 | ``` 55 | -------------------------------------------------------------------------------- /packages/responsive-card/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pansy/react-responsive-card", 3 | "version": "0.2.1", 4 | "main": "lib/index.js", 5 | "module": "es/index.js", 6 | "types": "lib/index.d.ts", 7 | "files": [ 8 | "es", 9 | "lib" 10 | ], 11 | "scripts": { 12 | "build": "redbud build" 13 | }, 14 | "peerDependencies": { 15 | "react": ">=16.9.0", 16 | "react-dom": ">=16.9.0" 17 | }, 18 | "dependencies": { 19 | "@pansy/shared": "^1.9.0", 20 | "@pansy/use-size": "^0.3.1" 21 | }, 22 | "publishConfig": { 23 | "registry": "https://registry.npmjs.org", 24 | "access": "public" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/responsive-card/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState, useEffect } from 'react'; 2 | import { useSize } from '@pansy/use-size'; 3 | import { getAdaptiveConfig, getGutter } from './utils'; 4 | 5 | import type { AdaptiveConfig } from './utils'; 6 | 7 | export interface ResponsiveCardProps { 8 | /** 额外的样式类 */ 9 | className?: string; 10 | /** 额外的样式 */ 11 | style?: React.CSSProperties; 12 | /** 13 | * 卡片的默认宽度 14 | * @default 260 15 | */ 16 | defaultWidth?: number; 17 | /** 18 | * 间距设置 19 | * @default 16 20 | */ 21 | gutter?: [number, number]; 22 | children?: ((config: AdaptiveConfig) => React.ReactNode) | React.ReactNode; 23 | } 24 | 25 | const rootStyle: React.CSSProperties = { 26 | position: 'relative', 27 | display: 'flex', 28 | flexDirection: 'row', 29 | flexWrap: 'wrap', 30 | } 31 | 32 | const defaultGutter = 16; 33 | 34 | export const ResponsiveCard: React.FC = ({ 35 | className, 36 | style, 37 | defaultWidth = 260, 38 | gutter = defaultGutter, 39 | children, 40 | }) => { 41 | const rootRef = useRef(null); 42 | const [adaptiveConfig, setAdaptiveConfig] = useState({} as AdaptiveConfig); 43 | 44 | /** 水平间隔 */ 45 | const horizontalGutter = getGutter(gutter, 0) ?? defaultGutter; 46 | /** 垂直间隔 */ 47 | const verticalGutter = getGutter(gutter, 1) ?? defaultGutter; 48 | const { width } = useSize(rootRef) ?? {}; 49 | 50 | useEffect( 51 | () => { 52 | setAdaptiveConfig(getAdaptiveConfig(width, { 53 | defaultWidth, 54 | defaultGutter: horizontalGutter 55 | })); 56 | }, 57 | [width, horizontalGutter, verticalGutter, defaultGutter] 58 | ); 59 | 60 | const renderChildren = () => { 61 | if (typeof children === 'function') { 62 | return children(adaptiveConfig); 63 | } 64 | 65 | /** 优化体验 */ 66 | if (adaptiveConfig.width === 0) { 67 | return null; 68 | } 69 | 70 | const { width = defaultWidth, gutter, span } = adaptiveConfig; 71 | 72 | return React.Children.map(children, (child: any, index: number) => { 73 | const style = { 74 | width, 75 | marginRight: 76 | (index + 1) % span != 0 77 | ? gutter 78 | : 0, 79 | } 80 | 81 | return React.cloneElement(child, { 82 | style 83 | }) 84 | }); 85 | } 86 | 87 | return ( 88 |
97 | {renderChildren()} 98 |
99 | ) 100 | } 101 | -------------------------------------------------------------------------------- /packages/responsive-card/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { isNumber } from '@pansy/shared'; 2 | 3 | export interface Opts { 4 | /** 默认宽度 */ 5 | defaultWidth?: number; 6 | /** 默认间距 */ 7 | defaultGutter?: number; 8 | } 9 | 10 | export interface AdaptiveConfig { 11 | /** 每项的宽度 */ 12 | width: number; 13 | /** 左右间距 */ 14 | gutter: number; 15 | /** 每行的数目 */ 16 | span: number; 17 | } 18 | 19 | /** 20 | * 获取适配结果 21 | * @param width 22 | * @param opts 23 | */ 24 | export const getAdaptiveConfig = ( 25 | width?: number, 26 | { defaultWidth = 260, defaultGutter = 16 }: Opts = {}, 27 | ) => { 28 | const adaptiveConfig: AdaptiveConfig = { 29 | width: 0, 30 | gutter: 0, 31 | span: 0, 32 | }; 33 | 34 | if (!isNumber(width) || width < defaultWidth) return adaptiveConfig; 35 | 36 | // 只能放置一个卡片 37 | if (width <= defaultWidth * 2) { 38 | adaptiveConfig.width = width; 39 | adaptiveConfig.gutter = 0; 40 | adaptiveConfig.span = 1; 41 | } 42 | 43 | const num = Math.floor(width / defaultWidth); 44 | const defaultGutterTotal = (num - 1) * defaultGutter; 45 | 46 | if (width > defaultGutterTotal + defaultWidth * num) { 47 | adaptiveConfig.width = (width - defaultGutterTotal) / num; 48 | adaptiveConfig.gutter = defaultGutter; 49 | adaptiveConfig.span = num; 50 | } else { 51 | adaptiveConfig.width = (width - (num - 2) * defaultGutter) / (num - 1); 52 | adaptiveConfig.gutter = defaultGutter; 53 | adaptiveConfig.span = num - 1; 54 | } 55 | 56 | return adaptiveConfig; 57 | }; 58 | 59 | export function getGutter(value: number | number[], index: 0 | 1): number | undefined { 60 | if (Array.isArray(value)) { 61 | return value[index]; 62 | } 63 | 64 | return value; 65 | } 66 | -------------------------------------------------------------------------------- /packages/split-screen/.redbudrc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'redbud'; 2 | 3 | export default defineConfig({ 4 | extends: '../../.redbudrc.base.ts' 5 | }); 6 | -------------------------------------------------------------------------------- /packages/split-screen/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [1.2.1](https://github.com/pansyjs/react-components/compare/@pansy/react-split-screen@1.2.0...@pansy/react-split-screen@1.2.1) (2023-02-02) 7 | 8 | **Note:** Version bump only for package @pansy/react-split-screen 9 | 10 | 11 | 12 | 13 | 14 | # [1.2.0](https://github.com/pansyjs/react-components/compare/@pansy/react-split-screen@1.1.1...@pansy/react-split-screen@1.2.0) (2021-11-04) 15 | 16 | 17 | ### Features 18 | 19 | * **split-screen:** 添加插入视频场景 ([f70886b](https://github.com/pansyjs/react-components/commit/f70886b4388bd58f1c8ceb1732b7367ef45d5811)) 20 | 21 | 22 | 23 | 24 | 25 | ## [1.1.1](https://github.com/pansyjs/react-components/compare/@pansy/react-split-screen@1.1.0...@pansy/react-split-screen@1.1.1) (2021-07-12) 26 | 27 | **Note:** Version bump only for package @pansy/react-split-screen 28 | 29 | 30 | 31 | 32 | 33 | # 1.1.0 (2021-04-13) 34 | 35 | 36 | ### Features 37 | 38 | * add @pansy/react-split-screen ([1527319](https://github.com/pansyjs/react-components/commit/15273193de7c5d22998cd4596d83d2b38604a08d)) 39 | -------------------------------------------------------------------------------- /packages/split-screen/README.md: -------------------------------------------------------------------------------- 1 |

@pansy/react-split-screen

2 | 3 | 分屏组件 4 | 5 | ## 🏗 安装 6 | 7 | ```sh 8 | # npm 安装 9 | npm install --save @pansy/react-split-screen 10 | 11 | # yarn 安装 12 | yarn add @pansy/react-split-screen 13 | ``` 14 | 15 | ## 🔨 使用 16 | 17 | ```tsx 18 | import React from 'react'; 19 | import SplitScreen from '@pansy/react-split-screen'; 20 | 21 | export default () => { 22 | return ( 23 |
24 | > 25 |
26 | ); 27 | }; 28 | ``` 29 | -------------------------------------------------------------------------------- /packages/split-screen/docs/demo/demo-01.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Select, Space } from 'antd'; 3 | import SplitScreen from '@pansy/react-split-screen'; 4 | 5 | const list = [1, 4, 6, 8, 9, 13 , 16]; 6 | 7 | const options = list.map(item => ({ value: item, label: item })) 8 | 9 | export default () => { 10 | const [current, setCurrent] = useState(4); 11 | 12 | return ( 13 | 14 | { setCurrent(value) }} 21 | /> 22 |
23 | 24 | {(index: number) => { 25 | return ( 26 | 33 | ) 34 | }} 35 | 36 |
37 |
38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /packages/split-screen/docs/demo/demo-03.less: -------------------------------------------------------------------------------- 1 | .item { 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | } 6 | 7 | .itemActive { 8 | border: 1px solid red; 9 | } 10 | -------------------------------------------------------------------------------- /packages/split-screen/docs/demo/demo-03.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useMemo } from 'react'; 2 | import { classNames } from '@pansy/shared' 3 | import { Select, Space, Input, Button, message } from 'antd'; 4 | import SplitScreen from '@pansy/react-split-screen'; 5 | import Player from '@pansy/react-aliplayer'; 6 | import type { SplitScreenAmount } from '@pansy/react-split-screen/es/types'; 7 | import styles from './demo-03.less'; 8 | 9 | const list: SplitScreenAmount[] = [1, 4, 6, 8, 9, 13, 16]; 10 | 11 | const options = list.map(item => ({ value: item, label: item })); 12 | 13 | interface PlayerItemProps { 14 | url?: string; 15 | active?: boolean; 16 | onClick?: () => void; 17 | } 18 | 19 | const PlayerItem: React.FC = ({ 20 | url, 21 | active, 22 | onClick, 23 | }) => { 24 | const player = useMemo( 25 | () => { 26 | return ( 27 | 28 | ) 29 | }, 30 | [url] 31 | ); 32 | 33 | return ( 34 |
40 | {url && player} 41 |
42 | ) 43 | } 44 | 45 | export default () => { 46 | const [currentAmount, setCurrentAmount] = useState(list[1]); 47 | const [videos, setVideos] = useState([]); 48 | const [inputValue, setInputValue] = useState(''); 49 | const [currentWindowIndex, setCurrentWindowIndex] = useState(0); 50 | 51 | useEffect( 52 | () => { 53 | setVideos(prev => { 54 | if (prev.length > currentAmount) { 55 | return prev.splice(0, currentAmount); 56 | } 57 | 58 | if (prev.length < currentAmount) { 59 | for (let i = 0; i < currentAmount- prev.length; i++) { 60 | prev.push(''); 61 | } 62 | } 63 | 64 | return prev; 65 | }) 66 | }, 67 | [currentAmount] 68 | ); 69 | 70 | const handleSelect = (index: number) => { 71 | setCurrentWindowIndex(index); 72 | } 73 | 74 | const handleAdd = () => { 75 | if (!inputValue) { 76 | message.warning('视频地址不能为空'); 77 | return; 78 | } 79 | setVideos(prev => { 80 | const next = [...prev]; 81 | next[currentWindowIndex] = inputValue; 82 | console.log(next); 83 | return next; 84 | }); 85 | } 86 | 87 | return ( 88 | 89 |
90 | { 103 | setInputValue(e.target.value); 104 | }} 105 | allowClear 106 | placeholder="请填写视频地址" 107 | /> 108 | 111 | 112 |
113 | 114 | 115 |
116 | amount={currentAmount} list={videos}> 117 | {(index: number, url) => ( 118 | { 121 | handleSelect(index); 122 | }} 123 | url={url} 124 | /> 125 | )} 126 | 127 |
128 |
129 | ) 130 | } 131 | -------------------------------------------------------------------------------- /packages/split-screen/docs/split-screen.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: SplitScreen 分屏组件 3 | nav: 4 | title: 组件 5 | path: /components 6 | order: 1 7 | group: 8 | path: /basic 9 | title: '基础组件' 10 | order: 0 11 | --- 12 | 13 | # SplitScreen 分屏组件 14 | 15 | ## 何时使用 16 | 17 | - 需要将页面切割成多份展示时使用。 18 | 19 | ## 代码演示 20 | 21 | ### 基础示例 22 | 23 | 24 | 25 | ### 场景一 26 | 27 | 28 | 29 | ## API 30 | 31 | | 参数 | 说明 | 类型 | 默认值 | 版本 | 32 | | ------------ | --------------| ------------------- | ------ | ---- | 33 | | className | 额外的样式类 | `string` | -- | -- | 34 | | style | 额外的样式 | `React.CSSProperties` | -- | -- | 35 | | amount | 当前的分屏数量 | `number` | `4` | -- | 36 | | gutter | 分屏的间隔 | `number` | `8` | -- | 37 | | background | 分屏的背景色 | `string` | `#000` | -- | 38 | | children | 需要展示的业务内容 | `(index: number) => React.ReactNode` |-- | -- | 39 | -------------------------------------------------------------------------------- /packages/split-screen/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pansy/react-split-screen", 3 | "version": "1.2.1", 4 | "license": "MIT", 5 | "main": "lib/index.js", 6 | "module": "es/index.js", 7 | "types": "es/index.d.ts", 8 | "files": [ 9 | "es", 10 | "lib" 11 | ], 12 | "scripts": { 13 | "build": "redbud build" 14 | }, 15 | "peerDependencies": { 16 | "react": ">=16.9.0", 17 | "react-dom": ">=16.9.0" 18 | }, 19 | "publishConfig": { 20 | "registry": "https://registry.npmjs.org", 21 | "access": "public" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/split-screen/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { getVideoWindowStyle } from './utils'; 3 | import type { SplitScreenAmount } from './types'; 4 | 5 | export interface SplitScreenProps { 6 | /** 额外的样式类 */ 7 | className?: string; 8 | /** 额外的样式 */ 9 | style?: React.CSSProperties; 10 | /** 当前的分屏数量 */ 11 | amount: SplitScreenAmount; 12 | /** 需要用到的数据池 */ 13 | list?: D[]; 14 | /** 分屏的间隔 */ 15 | gutter?: number; 16 | /** 分屏的背景色 */ 17 | background?: string; 18 | /** 业务相关 */ 19 | children: (index: number, data: D) => React.ReactNode; 20 | } 21 | 22 | const containerStyle: React.CSSProperties = { 23 | position: 'relative', 24 | width: '100%', 25 | height: '100%', 26 | } 27 | 28 | export function SplitScreen({ 29 | className, 30 | style, 31 | amount = 4, 32 | list = [], 33 | gutter = 8, 34 | background = "#000", 35 | children, 36 | }: SplitScreenProps) { 37 | const [items, setItems] = useState(new Array(amount).fill(undefined)); 38 | 39 | useEffect( 40 | () => { 41 | if (amount) { 42 | setItems(new Array(amount).fill(undefined)); 43 | } 44 | }, 45 | [amount] 46 | ) 47 | 48 | const renderChildren = (index: number) => { 49 | if (typeof children === 'function') { 50 | return children(index, list[index]); 51 | } 52 | 53 | return null 54 | } 55 | 56 | const contentStyle: React.CSSProperties = { 57 | position: 'absolute', 58 | width: `calc(100% + ${gutter}px)`, 59 | height: `calc(100% + ${gutter}px)`, 60 | margin: `-${gutter / 2}px -${gutter / 2}px`, 61 | } 62 | 63 | const itemStyle: React.CSSProperties = { 64 | position: 'relative', 65 | display: 'inline-block', 66 | padding: `${gutter / 2}px`, 67 | overflow: 'hidden', 68 | verticalAlign: 'top' 69 | } 70 | 71 | const itemContentStyle: React.CSSProperties = { 72 | width: '100%', 73 | height: '100%', 74 | background 75 | } 76 | 77 | return ( 78 |
79 |
80 | {items.map((_, index) => { 81 | const style = getVideoWindowStyle(amount, index); 82 | return ( 83 |
84 |
85 | {renderChildren(index)} 86 |
87 |
88 | ) 89 | })} 90 |
91 |
92 | ) 93 | } 94 | 95 | export default SplitScreen; 96 | -------------------------------------------------------------------------------- /packages/split-screen/src/types.ts: -------------------------------------------------------------------------------- 1 | /** 目前支持的分屏数 */ 2 | export type SplitScreenAmount = 3 | 1 | 4 | 4 | 5 | 6 | 6 | 8 | 7 | 9 | 8 | 13 | 9 | 16; 10 | -------------------------------------------------------------------------------- /packages/split-screen/src/utils.ts: -------------------------------------------------------------------------------- 1 | import type { CSSProperties } from 'react'; 2 | 3 | interface Config { 4 | rows: number; 5 | different: number; 6 | differentRows: number; 7 | } 8 | 9 | const weirdSplitScreenConfig: Record = { 10 | 6: { 11 | rows: 3, 12 | different: 1, 13 | differentRows: 2, 14 | }, 15 | 8: { 16 | rows: 4, 17 | different: 1, 18 | differentRows: 3, 19 | } 20 | } 21 | 22 | /** 23 | * 获取窗口的样式 24 | * @param amount 分屏数 25 | * @param amount 分屏的间隔 26 | * @param index 分屏的索引 27 | * @returns 28 | */ 29 | export const getVideoWindowStyle = ( 30 | amount: number, 31 | index: number 32 | ) => { 33 | let style: CSSProperties = {}; 34 | 35 | const sqrt = Math.sqrt(amount); 36 | 37 | // n*n >> 1、4、6、8 38 | if (Number.isInteger(sqrt)) { 39 | style = { 40 | width: `calc(100% / ${sqrt})`, 41 | height: `calc(100% / ${sqrt})`, 42 | } 43 | return style; 44 | } 45 | 46 | // 6、8 47 | const config = weirdSplitScreenConfig[amount]; 48 | 49 | if (config) { 50 | style = { 51 | float: 'left', 52 | width: `${100 /config.rows}%`, 53 | height: `${100 /config.rows}%`, 54 | } 55 | 56 | if (index + 1 === config.different) { 57 | style.width = `calc(${(100 / config.rows) * config.differentRows}%)`; 58 | style.height = `calc(${(100 / config.rows) * config.differentRows}% - 1px)`; 59 | } 60 | 61 | return style; 62 | }; 63 | 64 | // 13 65 | const splitScreenConfig = { 66 | rows: 4, 67 | different: 6, 68 | differentRows: 2 69 | } 70 | 71 | style = { 72 | position: 'absolute', 73 | width: `25%`, 74 | height: `25%` 75 | } 76 | 77 | if (index + 1 === splitScreenConfig.different) { 78 | style.width = `calc(${(100 / splitScreenConfig.rows) * splitScreenConfig.differentRows}%)`; 79 | style.height = `calc(${(100 / splitScreenConfig.rows) * splitScreenConfig.differentRows}%)`; 80 | } 81 | 82 | if (index >= 0 && index <= 3) { 83 | style.top = 0; 84 | style.left = `${index * 25}%`; 85 | } 86 | 87 | if (index === 4) { 88 | style.top = '25%'; 89 | style.left = 0; 90 | } 91 | 92 | if (splitScreenConfig.different === index + 1) { 93 | style.top = '25%'; 94 | style.left = '25%'; 95 | } 96 | 97 | if (index === 6) { 98 | style.top = '25%'; 99 | style.left = '75%'; 100 | } 101 | 102 | if (index === 7) { 103 | style.top = `calc(50%)`; 104 | style.left = 0; 105 | } 106 | 107 | if (index === 8) { 108 | style.top = `calc(50%)`; 109 | style.left = '75%'; 110 | } 111 | 112 | if (index >= 9 && index <= 12) { 113 | style.top = `calc(75%)`; 114 | style.left = `${(index - 9) * 25}%`; 115 | } 116 | 117 | return style; 118 | } 119 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | - 'scripts' 4 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "ignoreDeps": ["@types/react", "@types/react-dom"] 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "target": "esnext", 5 | "moduleResolution": "node", 6 | "jsx": "preserve", 7 | "esModuleInterop": true, 8 | "experimentalDecorators": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noImplicitReturns": true, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "declaration": true, 14 | "skipLibCheck": true, 15 | "paths": {} 16 | }, 17 | "include": [ 18 | "**/src/**/*", 19 | "**/docs/**/*", 20 | "scripts/**/*", 21 | "**/demos", 22 | ".eslintrc.js", 23 | "tests", 24 | "jest.config.js", 25 | "**/fixtures", 26 | "./tests/no-duplicated.ts" 27 | ] 28 | } 29 | --------------------------------------------------------------------------------