mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-19 03:11:20 -08:00
fix(react): [EX-01] verify installed consumers and clean up callback failures
This commit is contained in:
1 parent
3e39fc745f
commit
e0d785662d
25 files changed
+3539
-98
No files matched your search
@@ -1,12 +1,60 @@
|
||||
# React + Vite
|
||||
# React consumer example
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
Run from the repository root with Node from .node-version and Yarn Classic
|
||||
1.22.22. Install once with yarn install --frozen-lockfile; keep only the root
|
||||
yarn.lock in version control.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
```sh
|
||||
yarn dev:react
|
||||
yarn typecheck:react
|
||||
yarn lint:react
|
||||
yarn build:react
|
||||
yarn test:react-consumer
|
||||
```
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
build:react writes to refactor/.cache/react-example-dist. It uses built
|
||||
workspace package entrypoints. Rebuild a changed library with `yarn build <package>` before testing its integration. The consumer test packs the current
|
||||
core, Danmuku and Document PiP distributions, installs them outside the workspace,
|
||||
verifies their bytes, typechecks and builds the copied example, and exercises it
|
||||
in Chromium, Firefox and WebKit. No package aliases to source files are used.
|
||||
Reports and the consumer lock are in unique refactor/.cache/react-consumer-* folders.
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
## Component contract and ownership
|
||||
|
||||
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
|
||||
- src/Player.tsx owns one Artplayer instance per effect. Existing props remain
|
||||
option: `Partial<Option>`, optional getInstance(art), and native div attributes.
|
||||
Stable option/callback identities retain the instance. Changing either destroys
|
||||
the old instance before creating one.
|
||||
- getInstance runs after construction, not media readiness. Subscribe to player
|
||||
events for readiness. React StrictMode may call it again during development
|
||||
setup/cleanup. Callback errors destroy the instance before propagating the
|
||||
original error to React, including when cleanup also throws.
|
||||
- Cleanup uses destroy(false): React owns the outer div. Retained player references
|
||||
expose isDestroy; the callback does not receive a new null argument on unmount.
|
||||
- src/player-options.ts retains this example's existing overrides: French language,
|
||||
French/Indonesian dictionaries, and new Danmuku/Document PiP factories per instance.
|
||||
These still override option.lang, i18n and plugins. Other options pass through.
|
||||
React supplies the container. The url assertion preserves the historical Partial
|
||||
input; the core validates it. Missing URLs are not promised to construct successfully.
|
||||
- src/App.tsx supplies stable options and a callback. src/main.tsx mounts StrictMode;
|
||||
index.html loads the TSX entry. tsconfig.json checks the actual TSX and declarations
|
||||
with strict checks and skipLibCheck: false.
|
||||
- Root development dependencies pin React/React DOM 19.1.1, their types
|
||||
19.1.10/19.1.7, Vite 7.3.6, React Vite plugin 5.0.0 and TS 5.9.3.
|
||||
These are example/test tooling, not Artplayer runtime dependencies.
|
||||
|
||||
## Test scope
|
||||
|
||||
Installed development and production builds cover StrictMode, stable/changed
|
||||
props, sibling instances, optional callbacks, unmount/remount, callback exceptions,
|
||||
native play/pause/seek, decoded canvas pixels, plugin registration and the App entry.
|
||||
Video and XML responses are controlled fixtures; CDN availability is not asserted.
|
||||
Window PiP interaction, full Danmuku scheduling, macOS/iOS Safari and device
|
||||
capabilities remain in package/release acceptance tasks.
|
||||
|
||||
yarn test:react-consumer --before substitutes recorded pre-fix Player source
|
||||
against the same installed candidate packages. It must fail at callback exception
|
||||
cleanup. This is a wrapper-source regression control, not an old npm wrapper test:
|
||||
this example is not a published wrapper package.
|
||||
|
||||
Maintenance details and frozen evidence belong to EX-01 in refactor/.
|
||||
@@ -1,29 +1,2 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{js,jsx}'],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
reactHooks.configs['recommended-latest'],
|
||||
reactRefresh.configs.vite,
|
||||
],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
ecmaFeatures: { jsx: true },
|
||||
sourceType: 'module',
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
|
||||
},
|
||||
},
|
||||
])
|
||||
// Reuse the repository's TypeScript-aware configuration for this TSX example.
|
||||
export { default } from '../../eslint.config.js'
|
||||
@@ -8,6 +8,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,30 +1,29 @@
|
||||
{
|
||||
"name": "artplayer-react",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"packageManager": "yarn@1.22.22",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"build": "yarn typecheck && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
"preview": "vite preview",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"artplayer": "^5.3.0-beta.4",
|
||||
"artplayer-plugin-danmuku": "^5.2.0",
|
||||
"artplayer-plugin-document-pip": "^1.0.2",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1"
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.33.0",
|
||||
"@types/react": "^19.1.10",
|
||||
"@types/react-dom": "^19.1.7",
|
||||
"@vitejs/plugin-react": "^5.0.0",
|
||||
"eslint": "^9.33.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.20",
|
||||
"globals": "^16.3.0",
|
||||
"vite": "^7.1.2"
|
||||
"@types/react": "19.1.10",
|
||||
"@types/react-dom": "19.1.7",
|
||||
"@vitejs/plugin-react": "5.0.0",
|
||||
"eslint": "9.39.2",
|
||||
"typescript": "5.9.3",
|
||||
"vite": "7.3.6"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import Artplayer, { type Option } from 'artplayer'
|
||||
import type Artplayer from 'artplayer'
|
||||
import type { Option } from 'artplayer'
|
||||
import Player from './Player'
|
||||
|
||||
function getInstance(art: Artplayer) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(art)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
import type { Option } from 'artplayer'
|
||||
import type { HTMLAttributes } from 'react'
|
||||
import Artplayer from 'artplayer'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import Artplayer, { type Option } from 'artplayer'
|
||||
import { playerOptions } from './player-options'
|
||||
|
||||
// Test i18n
|
||||
import fr from 'artplayer/i18n/fr'
|
||||
import id from 'artplayer/i18n/id'
|
||||
|
||||
// Test plugins
|
||||
import artplayerPluginDanmuku from 'artplayer-plugin-danmuku'
|
||||
import artplayerPluginDocumentPip from 'artplayer-plugin-document-pip'
|
||||
|
||||
interface PlayerProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
option: Partial<Option>,
|
||||
export interface PlayerProps extends HTMLAttributes<HTMLDivElement> {
|
||||
option: Partial<Option>
|
||||
getInstance?: (art: Artplayer) => void
|
||||
}
|
||||
|
||||
@@ -18,24 +13,25 @@ export default function Player({ option, getInstance, ...rest }: PlayerProps) {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return
|
||||
if (!containerRef.current)
|
||||
return
|
||||
|
||||
const art = new Artplayer({
|
||||
...option,
|
||||
container: containerRef.current,
|
||||
url: option.url as string,
|
||||
i18n: { id, fr },
|
||||
lang: 'fr',
|
||||
plugins: [
|
||||
artplayerPluginDocumentPip({}),
|
||||
artplayerPluginDanmuku({
|
||||
danmuku: 'https://artplayer.org/assets/sample/danmuku.xml',
|
||||
}),
|
||||
],
|
||||
})
|
||||
const art = new Artplayer(playerOptions(option, containerRef.current))
|
||||
|
||||
if (typeof getInstance === 'function') {
|
||||
getInstance(art)
|
||||
try {
|
||||
if (typeof getInstance === 'function') {
|
||||
getInstance(art)
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
try {
|
||||
art.destroy(false)
|
||||
}
|
||||
finally {
|
||||
// Keep the consumer's error even if teardown also fails.
|
||||
// eslint-disable-next-line no-unsafe-finally
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { Option } from 'artplayer'
|
||||
import artplayerPluginDanmuku from 'artplayer-plugin-danmuku'
|
||||
import artplayerPluginDocumentPip from 'artplayer-plugin-document-pip'
|
||||
import fr from 'artplayer/i18n/fr'
|
||||
import id from 'artplayer/i18n/id'
|
||||
|
||||
// Preserve this example's existing overrides and create plugins per instance.
|
||||
export function playerOptions(option: Partial<Option>, container: HTMLDivElement): Option {
|
||||
return {
|
||||
...option,
|
||||
container,
|
||||
// Partial<Option> is the historical wrapper API; Artplayer validates missing URLs.
|
||||
url: option.url as string,
|
||||
i18n: { id, fr },
|
||||
lang: 'fr',
|
||||
plugins: [
|
||||
artplayerPluginDocumentPip({}),
|
||||
artplayerPluginDanmuku({
|
||||
danmuku: 'https://artplayer.org/assets/sample/danmuku.xml',
|
||||
}),
|
||||
],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"jsx": "react-jsx",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["react", "react-dom"],
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": false
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
|
||||
Reference in new issue
Block a user