zwplayer-react 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +550 -0
- package/lib/index.esm.js +585 -0
- package/lib/index.js +591 -0
- package/package.json +60 -0
- package/scripts/install.js +177 -0
- package/zwplayerlib/css/openhand.cur +0 -0
- package/zwplayerlib/css/zwplayer.css +1 -0
- package/zwplayerlib/css/zwplayer.ttf +0 -0
- package/zwplayerlib/css/zwplayer.woff +0 -0
- package/zwplayerlib/css/zwplayer.woff2 +0 -0
- package/zwplayerlib/plugins/dash.all.min.js +3 -0
- package/zwplayerlib/plugins/flv.live.js +358 -0
- package/zwplayerlib/plugins/flv.min.js +2 -0
- package/zwplayerlib/plugins/hls.light.min.js +2 -0
- package/zwplayerlib/plugins/hls.min.js +2 -0
- package/zwplayerlib/plugins/mpeg.min.js +9 -0
- package/zwplayerlib/widgets/danmu_setting.html +116 -0
- package/zwplayerlib/widgets/smiles/p1.png +0 -0
- package/zwplayerlib/widgets/smiles/p10.png +0 -0
- package/zwplayerlib/widgets/smiles/p11.png +0 -0
- package/zwplayerlib/widgets/smiles/p12.png +0 -0
- package/zwplayerlib/widgets/smiles/p13.png +0 -0
- package/zwplayerlib/widgets/smiles/p14.png +0 -0
- package/zwplayerlib/widgets/smiles/p15.png +0 -0
- package/zwplayerlib/widgets/smiles/p16.png +0 -0
- package/zwplayerlib/widgets/smiles/p17.png +0 -0
- package/zwplayerlib/widgets/smiles/p18.png +0 -0
- package/zwplayerlib/widgets/smiles/p19.png +0 -0
- package/zwplayerlib/widgets/smiles/p2.png +0 -0
- package/zwplayerlib/widgets/smiles/p20.png +0 -0
- package/zwplayerlib/widgets/smiles/p21.png +0 -0
- package/zwplayerlib/widgets/smiles/p22.png +0 -0
- package/zwplayerlib/widgets/smiles/p23.png +0 -0
- package/zwplayerlib/widgets/smiles/p24.png +0 -0
- package/zwplayerlib/widgets/smiles/p25.png +0 -0
- package/zwplayerlib/widgets/smiles/p26.png +0 -0
- package/zwplayerlib/widgets/smiles/p27.png +0 -0
- package/zwplayerlib/widgets/smiles/p28.png +0 -0
- package/zwplayerlib/widgets/smiles/p29.png +0 -0
- package/zwplayerlib/widgets/smiles/p3.png +0 -0
- package/zwplayerlib/widgets/smiles/p30.png +0 -0
- package/zwplayerlib/widgets/smiles/p31.png +0 -0
- package/zwplayerlib/widgets/smiles/p32.png +0 -0
- package/zwplayerlib/widgets/smiles/p33.png +0 -0
- package/zwplayerlib/widgets/smiles/p34.png +0 -0
- package/zwplayerlib/widgets/smiles/p35.png +0 -0
- package/zwplayerlib/widgets/smiles/p36.png +0 -0
- package/zwplayerlib/widgets/smiles/p37.png +0 -0
- package/zwplayerlib/widgets/smiles/p38.png +0 -0
- package/zwplayerlib/widgets/smiles/p39.png +0 -0
- package/zwplayerlib/widgets/smiles/p4.png +0 -0
- package/zwplayerlib/widgets/smiles/p40.png +0 -0
- package/zwplayerlib/widgets/smiles/p5.png +0 -0
- package/zwplayerlib/widgets/smiles/p6.png +0 -0
- package/zwplayerlib/widgets/smiles/p7.png +0 -0
- package/zwplayerlib/widgets/smiles/p8.png +0 -0
- package/zwplayerlib/widgets/smiles/p9.png +0 -0
- package/zwplayerlib/zwplayer.js +940 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var react = require('react');
|
|
6
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
+
|
|
8
|
+
// zwplayer tools - dynamic script loader for React
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Dynamically load JavaScript file
|
|
12
|
+
* @param {string} id - Script element ID
|
|
13
|
+
* @param {string} url - Script URL
|
|
14
|
+
* @param {function} cb - Callback function when loaded
|
|
15
|
+
* @returns {boolean} - Whether script was already loaded
|
|
16
|
+
*/
|
|
17
|
+
const zwplayer_loadjs = function (id, url, cb) {
|
|
18
|
+
let headNode = document.getElementsByTagName("head");
|
|
19
|
+
if (headNode.length == 0) {
|
|
20
|
+
headNode = document.createElement("head");
|
|
21
|
+
let body = document.getElementsByTagName("body");
|
|
22
|
+
if (body.length > 0) {
|
|
23
|
+
body = body[0];
|
|
24
|
+
document.insertBefore(headNode, body);
|
|
25
|
+
} else {
|
|
26
|
+
alert('Invalid html document: no HEAD tag');
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
headNode = headNode[0];
|
|
31
|
+
const nodes = headNode.childNodes;
|
|
32
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
33
|
+
const node = nodes[i];
|
|
34
|
+
if (node.nodeName.toUpperCase() === 'SCRIPT') {
|
|
35
|
+
if (node.id && node.id == id) {
|
|
36
|
+
if (node.getAttribute('loadstate') != 'true') {
|
|
37
|
+
const checkLoad = function () {
|
|
38
|
+
if (node.getAttribute('loadstate') != 'true') {
|
|
39
|
+
setTimeout(checkLoad, 50);
|
|
40
|
+
} else {
|
|
41
|
+
if (typeof cb == 'function') cb(id);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
setTimeout(checkLoad, 50);
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
if (typeof cb == 'function') cb(id);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const script = document.createElement("script");
|
|
54
|
+
script.type = "text/javascript";
|
|
55
|
+
if (script.readyState) {
|
|
56
|
+
// IE
|
|
57
|
+
script.onreadystatechange = function () {
|
|
58
|
+
if (script.readyState == "loaded" || script.readyState == "complete") {
|
|
59
|
+
script.onreadystatechange = null;
|
|
60
|
+
script.setAttribute('loadstate', 'true');
|
|
61
|
+
if (typeof cb == 'function') cb(id);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
} else {
|
|
65
|
+
script.onload = function () {
|
|
66
|
+
script.setAttribute('loadstate', 'true');
|
|
67
|
+
script.setAttribute('charset', 'utf-8');
|
|
68
|
+
if (typeof cb == 'function') cb(id);
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
script.id = id;
|
|
72
|
+
script.src = url;
|
|
73
|
+
headNode.appendChild(script);
|
|
74
|
+
return false;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
let useLocation = null;
|
|
78
|
+
try {
|
|
79
|
+
if (require && require('react-router-dom')) {
|
|
80
|
+
useLocation = require('react-router-dom').useLocation;
|
|
81
|
+
}
|
|
82
|
+
} catch (e) {
|
|
83
|
+
// react-router-dom 未安装,忽略
|
|
84
|
+
}
|
|
85
|
+
const zwplayer_js_def = '/zwplayer/zwplayer.js?v=3.6';
|
|
86
|
+
const availabe_props = {
|
|
87
|
+
isLive: 'boolean',
|
|
88
|
+
useOldFlv: 'boolean',
|
|
89
|
+
useFlv: 'boolean',
|
|
90
|
+
streamtype: 'string',
|
|
91
|
+
hasAudio: 'boolean',
|
|
92
|
+
hasVideo: 'boolean',
|
|
93
|
+
xmc_url: 'string',
|
|
94
|
+
fixedControlbar: 'boolean',
|
|
95
|
+
nativecontrols: 'boolean',
|
|
96
|
+
infoButton: 'boolean',
|
|
97
|
+
speedButton: 'boolean',
|
|
98
|
+
optionButton: 'boolean',
|
|
99
|
+
snapshotButton: 'boolean',
|
|
100
|
+
chapterButton: 'boolean',
|
|
101
|
+
castButton: 'boolean',
|
|
102
|
+
zoomButton: 'boolean',
|
|
103
|
+
annotationButton: 'boolean',
|
|
104
|
+
enableDanmu: 'boolean',
|
|
105
|
+
useProgressTooltip: 'boolean',
|
|
106
|
+
hidePlayBtn: 'boolean',
|
|
107
|
+
disablePlayBtn: 'boolean',
|
|
108
|
+
disableSeek: 'boolean',
|
|
109
|
+
disableFullscreenWin: 'boolean',
|
|
110
|
+
disablePicInPic: 'boolean',
|
|
111
|
+
disableVolumeControl: 'boolean',
|
|
112
|
+
lostFocusPause: 'boolean',
|
|
113
|
+
autoSmallWindow: 'boolean',
|
|
114
|
+
localPlayback: 'boolean',
|
|
115
|
+
recordButton: 'boolean',
|
|
116
|
+
segmentButton: 'boolean',
|
|
117
|
+
disableMutedConfirm: 'boolean',
|
|
118
|
+
keepAudioWindow: 'boolean',
|
|
119
|
+
lang: 'string',
|
|
120
|
+
thumbnails: 'object',
|
|
121
|
+
annotation: 'object',
|
|
122
|
+
annotations: 'object',
|
|
123
|
+
watermark: 'object',
|
|
124
|
+
watermarks: 'object',
|
|
125
|
+
logo: 'object|string',
|
|
126
|
+
sendDanmu: 'function',
|
|
127
|
+
danmuBarId: 'string',
|
|
128
|
+
fluid: 'boolean',
|
|
129
|
+
poster: 'string',
|
|
130
|
+
ratio: 'string|number',
|
|
131
|
+
width: 'string|number',
|
|
132
|
+
height: 'string|number',
|
|
133
|
+
timeFormat: 'string',
|
|
134
|
+
translateApi: 'string',
|
|
135
|
+
defVolume: 'number',
|
|
136
|
+
hideControlbarTimeout: 'number'
|
|
137
|
+
};
|
|
138
|
+
const ZwPlayer = /*#__PURE__*/react.forwardRef((props, ref) => {
|
|
139
|
+
const playerRef = react.useRef(null);
|
|
140
|
+
const zwplayerInstance = react.useRef(null);
|
|
141
|
+
// 仅在安装了 react-router-dom 时使用 useLocation
|
|
142
|
+
const location = useLocation ? useLocation() : null;
|
|
143
|
+
const [playerId] = react.useState(() => {
|
|
144
|
+
const now = Date.now();
|
|
145
|
+
if (!window.nextZWPlayerId) {
|
|
146
|
+
window.nextZWPlayerId = 1000;
|
|
147
|
+
}
|
|
148
|
+
const id = `zwplayer-${window.nextZWPlayerId}-${now}`;
|
|
149
|
+
window.nextZWPlayerId++;
|
|
150
|
+
return props.nodeid || id;
|
|
151
|
+
});
|
|
152
|
+
const [oldMurl, setOldMurl] = react.useState(null);
|
|
153
|
+
const [isDestroying, setIsDestroying] = react.useState(false);
|
|
154
|
+
|
|
155
|
+
// Helper function to check type
|
|
156
|
+
const isTypeOf = (type, types) => {
|
|
157
|
+
if (types.indexOf('|') > 0) {
|
|
158
|
+
const typeList = types.split('|');
|
|
159
|
+
for (let i = 0; i < typeList.length; i++) {
|
|
160
|
+
if (type === typeList[i]) return true;
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
return type === types;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// Initialize player
|
|
168
|
+
react.useEffect(() => {
|
|
169
|
+
const zwplayer_js = props.zwplayerlib || zwplayer_js_def;
|
|
170
|
+
let danmuBarId = null; // 保存弹幕控制栏ID
|
|
171
|
+
|
|
172
|
+
// Set root element styles
|
|
173
|
+
if (playerRef.current) {
|
|
174
|
+
playerRef.current.style.width = '100%';
|
|
175
|
+
playerRef.current.style.height = '100%';
|
|
176
|
+
playerRef.current.style.margin = '0';
|
|
177
|
+
playerRef.current.style.padding = '0';
|
|
178
|
+
}
|
|
179
|
+
zwplayer_loadjs('zwplayer-core', zwplayer_js, jsid => {
|
|
180
|
+
if (!jsid) {
|
|
181
|
+
console.log('zwplayer_loadjs failed!');
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 全局跟踪播放器实例,防止 StrictMode 重复创建
|
|
186
|
+
if (!window.__zwplayer_instances__) {
|
|
187
|
+
window.__zwplayer_instances__ = new Map();
|
|
188
|
+
}
|
|
189
|
+
const playerKey = playerId;
|
|
190
|
+
|
|
191
|
+
// 检查是否已经创建过播放器
|
|
192
|
+
// 只在 DOM 元素仍然存在时才复用实例
|
|
193
|
+
if (window.__zwplayer_instances__.has(playerKey)) {
|
|
194
|
+
const existingPlayer = window.__zwplayer_instances__.get(playerKey);
|
|
195
|
+
|
|
196
|
+
// 检查 DOM 元素是否还在文档中(防止路由切换后复用无效的实例)
|
|
197
|
+
const isDomInDocument = playerRef.current && document.body.contains(playerRef.current);
|
|
198
|
+
if (isDomInDocument && existingPlayer) {
|
|
199
|
+
console.log('⏭️ 播放器已存在,复用实例:', playerKey);
|
|
200
|
+
zwplayerInstance.current = existingPlayer;
|
|
201
|
+
return;
|
|
202
|
+
} else {
|
|
203
|
+
// DOM 元素不存在或已从文档移除,销毁旧实例并从缓存中移除
|
|
204
|
+
console.log('🧹 清理无效的播放器实例:', playerKey);
|
|
205
|
+
if (existingPlayer && typeof existingPlayer.destroy === 'function') {
|
|
206
|
+
try {
|
|
207
|
+
existingPlayer.destroy();
|
|
208
|
+
} catch (err) {
|
|
209
|
+
console.warn('销毁播放器实例时出错:', err);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
window.__zwplayer_instances__.delete(playerKey);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
let url = props.murl || '';
|
|
216
|
+
if (props.videoprop && typeof props.videoprop.url !== 'undefined') {
|
|
217
|
+
url = props.videoprop.url;
|
|
218
|
+
}
|
|
219
|
+
let videostyle = "width:100%;height:100%;";
|
|
220
|
+
if (props.videoprop && typeof props.videoprop.videostyle !== 'undefined') {
|
|
221
|
+
videostyle = props.videoprop.videostyle;
|
|
222
|
+
}
|
|
223
|
+
const params = {
|
|
224
|
+
url: url,
|
|
225
|
+
playerElm: playerRef.current,
|
|
226
|
+
videostyle: videostyle,
|
|
227
|
+
reconnect: props.reconnect !== undefined ? props.reconnect : true,
|
|
228
|
+
autoplay: props.autoplay !== undefined ? props.autoplay : true,
|
|
229
|
+
muted: props.muted,
|
|
230
|
+
controlbar: props.controlbar !== undefined ? props.controlbar : true,
|
|
231
|
+
useProgressTooltip: props.useProgressTooltip !== undefined ? props.useProgressTooltip : true,
|
|
232
|
+
onready: () => {
|
|
233
|
+
if (props.onready) props.onready(zwplayerInstance.current);
|
|
234
|
+
},
|
|
235
|
+
onnetclose: () => {
|
|
236
|
+
if (props.onnetclose) props.onnetclose(zwplayerInstance.current);
|
|
237
|
+
return true;
|
|
238
|
+
},
|
|
239
|
+
onFileSelected: event => {
|
|
240
|
+
if (props.onFileSelected) props.onFileSelected(event, zwplayerInstance.current);
|
|
241
|
+
},
|
|
242
|
+
onFileLoaded: event => {
|
|
243
|
+
if (props.onFileLoaded) props.onFileLoaded(event, zwplayerInstance.current);
|
|
244
|
+
},
|
|
245
|
+
onneterror: () => {
|
|
246
|
+
if (props.onneterror) props.onneterror(zwplayerInstance.current);
|
|
247
|
+
},
|
|
248
|
+
onmediaevent: event => {
|
|
249
|
+
if (props.onmediaevent) props.onmediaevent(event, zwplayerInstance.current);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// Add additional props from availabe_props
|
|
254
|
+
for (let prop in props) {
|
|
255
|
+
const propType = typeof props[prop];
|
|
256
|
+
if (prop in availabe_props && isTypeOf(propType, availabe_props[prop])) {
|
|
257
|
+
if (propType === 'object') params[prop] = JSON.parse(JSON.stringify(props[prop]));else params[prop] = props[prop];
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
console.log('🎬 创建新的播放器实例:', playerKey);
|
|
261
|
+
zwplayerInstance.current = new window.ZWPlayer(params);
|
|
262
|
+
window.__zwplayer_instances__.set(playerKey, zwplayerInstance.current);
|
|
263
|
+
if (params.danmuBarId) {
|
|
264
|
+
danmuBarId = params.danmuBarId; // 保存ID用于cleanup
|
|
265
|
+
|
|
266
|
+
// 使用全局变量跟踪已创建的弹幕控制栏
|
|
267
|
+
if (!window.__zwplayer_danmu_bars__) {
|
|
268
|
+
window.__zwplayer_danmu_bars__ = new Set();
|
|
269
|
+
}
|
|
270
|
+
const barKey = `${params.danmuBarId}`;
|
|
271
|
+
|
|
272
|
+
// 只有未创建过才创建
|
|
273
|
+
if (!window.__zwplayer_danmu_bars__.has(barKey)) {
|
|
274
|
+
console.log('✨ 创建新的弹幕控制栏:', barKey);
|
|
275
|
+
zwplayerInstance.current.buildDanmuControlbar(params.danmuBarId, 'react-zwp-danmubar');
|
|
276
|
+
window.__zwplayer_danmu_bars__.add(barKey);
|
|
277
|
+
} else {
|
|
278
|
+
console.log('⏭️ 弹幕控制栏已存在,跳过创建:', barKey);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
setOldMurl(url);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// Cleanup on unmount
|
|
285
|
+
return () => {
|
|
286
|
+
console.log('🧹 组件卸载,清理播放器:', playerId);
|
|
287
|
+
|
|
288
|
+
// 获取播放器实例
|
|
289
|
+
const player = zwplayerInstance.current;
|
|
290
|
+
|
|
291
|
+
// ===== 第一步:先禁用所有访问 DOM 的函数(防止 ResizeObserver 回调) =====
|
|
292
|
+
const dangerousFunctions = ['updateResponsiveControlbar', 'updateSpeedMenuMaxHeight', 'setupThemeCompatibility', 'checkButtonState'];
|
|
293
|
+
dangerousFunctions.forEach(funcName => {
|
|
294
|
+
if (player && typeof player[funcName] === 'function') {
|
|
295
|
+
player[funcName] = function () {
|
|
296
|
+
console.debug(`🔒 已禁用函数 ${funcName},防止访问已删除的 DOM`);
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// ===== 第二步:断开 ResizeObserver(在 stop 之前) =====
|
|
302
|
+
if (player && player.responsiveControlbarObserver) {
|
|
303
|
+
try {
|
|
304
|
+
player.responsiveControlbarObserver.disconnect();
|
|
305
|
+
console.log('✅ responsiveControlbarObserver 已断开');
|
|
306
|
+
} catch (err) {
|
|
307
|
+
console.warn('断开 responsiveControlbarObserver 时出错:', err);
|
|
308
|
+
}
|
|
309
|
+
player.responsiveControlbarObserver = null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// ===== 第三步:清理定时器(在 stop 之前) =====
|
|
313
|
+
if (player) {
|
|
314
|
+
// 清理 theme compatibility 定时器
|
|
315
|
+
if (player._themeCompatInterval) {
|
|
316
|
+
clearInterval(player._themeCompatInterval);
|
|
317
|
+
delete player._themeCompatInterval;
|
|
318
|
+
console.log('✅ _themeCompatInterval 已清除');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// 清理其他定时器
|
|
322
|
+
const timerProps = ['controlbarVisibilityTimer', 'hideControlbarTimer', 'overlayLayerClickTimer', 'toastTimerId', 'debounceTimer'];
|
|
323
|
+
timerProps.forEach(prop => {
|
|
324
|
+
if (player[prop]) {
|
|
325
|
+
clearTimeout(player[prop]);
|
|
326
|
+
player[prop] = null;
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// ===== 第四步:stop() - 对应 Vue 的 beforeDestroy =====
|
|
332
|
+
// 现在所有定时器和观察器都已清理,可以安全调用 stop
|
|
333
|
+
if (player && typeof player.stop === 'function') {
|
|
334
|
+
try {
|
|
335
|
+
player.stop();
|
|
336
|
+
console.log('✅ 播放器已停止 (对应Vue beforeDestroy)');
|
|
337
|
+
} catch (err) {
|
|
338
|
+
console.warn('停止播放器时出错:', err);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// 从全局缓存中移除
|
|
343
|
+
if (window.__zwplayer_instances__) {
|
|
344
|
+
window.__zwplayer_instances__.delete(playerId);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// 销毁播放器实例(释放资源)
|
|
348
|
+
if (player && typeof player.destroy === 'function') {
|
|
349
|
+
try {
|
|
350
|
+
// ===== 第五步:清理 ResizeHandler(最后清理 resize 相关的对象) =====
|
|
351
|
+
if (player.playerContainer) {
|
|
352
|
+
try {
|
|
353
|
+
// 获取并清空 resize listeners
|
|
354
|
+
const listeners = player.playerContainer.__z_resizeListeners;
|
|
355
|
+
if (listeners && Array.isArray(listeners)) {
|
|
356
|
+
listeners.length = 0;
|
|
357
|
+
delete player.playerContainer.__z_resizeListeners;
|
|
358
|
+
console.log('✅ resizeListeners 已清空');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// 移除 resize detector object
|
|
362
|
+
const detector = player.playerContainer.__resizeDetector__;
|
|
363
|
+
if (detector && detector.parentNode === player.playerContainer) {
|
|
364
|
+
player.playerContainer.removeChild(detector);
|
|
365
|
+
delete player.playerContainer.__resizeDetector__;
|
|
366
|
+
console.log('✅ resizeDetector 已移除');
|
|
367
|
+
}
|
|
368
|
+
} catch (err) {
|
|
369
|
+
console.warn('清理 resize 对象时出错:', err);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ===== 第六步:调用 destroy 方法 =====
|
|
374
|
+
player.destroy();
|
|
375
|
+
console.log('✅ 播放器实例已销毁:', playerId);
|
|
376
|
+
|
|
377
|
+
// ===== 第七步:最后的安全措施 =====
|
|
378
|
+
// 将 player 对象的所有方法设置为空函数,防止后续调用
|
|
379
|
+
Object.keys(player).forEach(key => {
|
|
380
|
+
if (typeof player[key] === 'function' && key !== 'destroy') {
|
|
381
|
+
player[key] = function () {};
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
} catch (err) {
|
|
385
|
+
console.warn('销毁播放器实例时出错:', err);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// 清理 ref 引用
|
|
390
|
+
zwplayerInstance.current = null;
|
|
391
|
+
|
|
392
|
+
// 清理弹幕控制栏并从全局记录中移除
|
|
393
|
+
if (danmuBarId) {
|
|
394
|
+
console.log('🧹 清理弹幕控制栏:', danmuBarId);
|
|
395
|
+
const danmuBar = document.getElementById(danmuBarId);
|
|
396
|
+
if (danmuBar) {
|
|
397
|
+
const controlBar = danmuBar.querySelector('.zwp-danmu-controlbar');
|
|
398
|
+
if (controlBar) {
|
|
399
|
+
controlBar.remove();
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// 从全局记录中移除
|
|
404
|
+
if (window.__zwplayer_danmu_bars__) {
|
|
405
|
+
window.__zwplayer_danmu_bars__.delete(danmuBarId);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
}, []); // Only run on mount
|
|
410
|
+
|
|
411
|
+
// 监听路由变化,提前禁用危险函数(使用 useLayoutEffect 确保在浏览器绘制前执行)
|
|
412
|
+
// 仅在使用 react-router-dom 时启用
|
|
413
|
+
react.useLayoutEffect(() => {
|
|
414
|
+
// 如果没有使用 react-router-dom,跳过路由监听
|
|
415
|
+
if (!location) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const player = zwplayerInstance.current;
|
|
419
|
+
|
|
420
|
+
// 当路由变化时,立即禁用可能访问 DOM 的函数
|
|
421
|
+
const disableDangerousFunctions = () => {
|
|
422
|
+
if (!player || isDestroying) return;
|
|
423
|
+
console.log('🔒 路由变化,提前禁用危险函数');
|
|
424
|
+
setIsDestroying(true);
|
|
425
|
+
const dangerousFunctions = ['updateResponsiveControlbar', 'updateSpeedMenuMaxHeight', 'setupThemeCompatibility', 'checkButtonState'];
|
|
426
|
+
dangerousFunctions.forEach(funcName => {
|
|
427
|
+
if (typeof player[funcName] === 'function') {
|
|
428
|
+
player[funcName] = function () {
|
|
429
|
+
// 空函数,什么都不做
|
|
430
|
+
console.debug(`🔒 函数 ${funcName} 已禁用(路由变化)`);
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
// 清理 debounceTimer
|
|
436
|
+
if (player.debounceTimer) {
|
|
437
|
+
clearTimeout(player.debounceTimer);
|
|
438
|
+
player.debounceTimer = null;
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
// 在路由即将变化时执行
|
|
443
|
+
disableDangerousFunctions();
|
|
444
|
+
|
|
445
|
+
// 清理时恢复标志
|
|
446
|
+
return () => {
|
|
447
|
+
setIsDestroying(false);
|
|
448
|
+
};
|
|
449
|
+
}, location ? [location.pathname] : []); // 仅在使用 react-router-dom 时监听路由变化
|
|
450
|
+
|
|
451
|
+
// Handle URL changes
|
|
452
|
+
react.useLayoutEffect(() => {
|
|
453
|
+
if (zwplayerInstance.current && props.murl && props.murl !== oldMurl) {
|
|
454
|
+
zwplayerInstance.current.play(props.murl);
|
|
455
|
+
setOldMurl(props.murl);
|
|
456
|
+
}
|
|
457
|
+
}, [props.murl, oldMurl]);
|
|
458
|
+
|
|
459
|
+
// Expose methods via ref
|
|
460
|
+
react.useImperativeHandle(ref, () => ({
|
|
461
|
+
play: (url, isLive, useOldFlv) => {
|
|
462
|
+
const playUrl = url || props.murl;
|
|
463
|
+
if (zwplayerInstance.current && playUrl) {
|
|
464
|
+
zwplayerInstance.current.play(playUrl, isLive, useOldFlv);
|
|
465
|
+
}
|
|
466
|
+
},
|
|
467
|
+
stop: () => {
|
|
468
|
+
if (zwplayerInstance.current) {
|
|
469
|
+
zwplayerInstance.current.stop();
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
pause: () => {
|
|
473
|
+
if (zwplayerInstance.current) {
|
|
474
|
+
zwplayerInstance.current.pause();
|
|
475
|
+
}
|
|
476
|
+
},
|
|
477
|
+
resume: () => {
|
|
478
|
+
if (zwplayerInstance.current) {
|
|
479
|
+
zwplayerInstance.current.resume();
|
|
480
|
+
}
|
|
481
|
+
},
|
|
482
|
+
getDuration: () => {
|
|
483
|
+
if (zwplayerInstance.current) {
|
|
484
|
+
return zwplayerInstance.current.getDuration();
|
|
485
|
+
}
|
|
486
|
+
return undefined;
|
|
487
|
+
},
|
|
488
|
+
getCurrentTime: () => {
|
|
489
|
+
if (zwplayerInstance.current) {
|
|
490
|
+
return zwplayerInstance.current.CurrentTime;
|
|
491
|
+
}
|
|
492
|
+
return undefined;
|
|
493
|
+
},
|
|
494
|
+
seekTime: time => {
|
|
495
|
+
if (zwplayerInstance.current) {
|
|
496
|
+
zwplayerInstance.current.seekTime(time);
|
|
497
|
+
}
|
|
498
|
+
},
|
|
499
|
+
destroy: () => {
|
|
500
|
+
if (zwplayerInstance.current) {
|
|
501
|
+
zwplayerInstance.current.destroy();
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
setplaystate: bPlaying => {
|
|
505
|
+
if (zwplayerInstance.current) {
|
|
506
|
+
zwplayerInstance.current.setplaystate(bPlaying);
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
setEnableDanmu: bEnable => {
|
|
510
|
+
if (zwplayerInstance.current) {
|
|
511
|
+
zwplayerInstance.current.setEnableDanmu(bEnable);
|
|
512
|
+
}
|
|
513
|
+
},
|
|
514
|
+
appendDanmu: (msgObj, setting) => {
|
|
515
|
+
if (zwplayerInstance.current) {
|
|
516
|
+
zwplayerInstance.current.appendDanmu(msgObj, setting);
|
|
517
|
+
}
|
|
518
|
+
},
|
|
519
|
+
setChapters: chapters => {
|
|
520
|
+
if (zwplayerInstance.current) {
|
|
521
|
+
zwplayerInstance.current.setChapters(chapters);
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
addSubtitle: (subtitleUrl, pos, title) => {
|
|
525
|
+
if (zwplayerInstance.current) {
|
|
526
|
+
zwplayerInstance.current.addSubtitle(subtitleUrl, pos, title);
|
|
527
|
+
}
|
|
528
|
+
},
|
|
529
|
+
loadSubtitle: (subtitleUrl, pos, title) => {
|
|
530
|
+
if (zwplayerInstance.current) {
|
|
531
|
+
zwplayerInstance.current.loadSubtitle(subtitleUrl, pos, title);
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
removeSubtitle: () => {
|
|
535
|
+
if (zwplayerInstance.current) {
|
|
536
|
+
zwplayerInstance.current.removeSubtitle();
|
|
537
|
+
}
|
|
538
|
+
},
|
|
539
|
+
buildDanmuControlbar: (parentId, className) => {
|
|
540
|
+
if (zwplayerInstance.current) {
|
|
541
|
+
zwplayerInstance.current.buildDanmuControlbar(parentId, className);
|
|
542
|
+
}
|
|
543
|
+
},
|
|
544
|
+
notifyResize: (width, height) => {
|
|
545
|
+
if (zwplayerInstance.current) {
|
|
546
|
+
zwplayerInstance.current.notifyResize(width, height);
|
|
547
|
+
}
|
|
548
|
+
},
|
|
549
|
+
setMute: bMute => {
|
|
550
|
+
if (zwplayerInstance.current) {
|
|
551
|
+
zwplayerInstance.current.setMuted(bMute);
|
|
552
|
+
}
|
|
553
|
+
},
|
|
554
|
+
setullscr: bFullScr => {
|
|
555
|
+
if (zwplayerInstance.current) {
|
|
556
|
+
zwplayerInstance.current.is_fullscr = bFullScr;
|
|
557
|
+
zwplayerInstance.current.setFullscreen(bFullScr);
|
|
558
|
+
}
|
|
559
|
+
},
|
|
560
|
+
lightOff: () => {
|
|
561
|
+
if (zwplayerInstance.current) {
|
|
562
|
+
zwplayerInstance.current.lightOff();
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
lightOn: () => {
|
|
566
|
+
if (zwplayerInstance.current) {
|
|
567
|
+
zwplayerInstance.current.lightOn();
|
|
568
|
+
}
|
|
569
|
+
},
|
|
570
|
+
get CurrentTime() {
|
|
571
|
+
if (zwplayerInstance.current) {
|
|
572
|
+
return zwplayerInstance.current.CurrentTime;
|
|
573
|
+
}
|
|
574
|
+
return -1;
|
|
575
|
+
},
|
|
576
|
+
get player() {
|
|
577
|
+
return zwplayerInstance.current;
|
|
578
|
+
}
|
|
579
|
+
}));
|
|
580
|
+
return /*#__PURE__*/jsxRuntime.jsx("div", {
|
|
581
|
+
ref: playerRef,
|
|
582
|
+
id: playerId,
|
|
583
|
+
className: "zwplayer-react",
|
|
584
|
+
style: props.style || {}
|
|
585
|
+
});
|
|
586
|
+
});
|
|
587
|
+
ZwPlayer.displayName = 'ZwPlayer';
|
|
588
|
+
|
|
589
|
+
exports.ZwPlayer = ZwPlayer;
|
|
590
|
+
exports.default = ZwPlayer;
|
|
591
|
+
exports.zwplayer_loadjs = zwplayer_loadjs;
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "zwplayer-react",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A React wrapper for ZWPlayer video player - lightweight, feature-rich web video player component",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"module": "lib/index.esm.js",
|
|
7
|
+
"types": "lib/index.d.ts",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=12.0.0"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"lib",
|
|
13
|
+
"zwplayerlib",
|
|
14
|
+
"scripts",
|
|
15
|
+
"README.md",
|
|
16
|
+
"USAGE.md",
|
|
17
|
+
"PUBLISH_GUIDE.md",
|
|
18
|
+
"QUICK_START.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "rollup -c rollup.config.mjs",
|
|
23
|
+
"prepublishOnly": "npm run build",
|
|
24
|
+
"postinstall": "node scripts/install.js",
|
|
25
|
+
"init": "node scripts/install.js",
|
|
26
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"react",
|
|
30
|
+
"video",
|
|
31
|
+
"player",
|
|
32
|
+
"html5",
|
|
33
|
+
"hls",
|
|
34
|
+
"flv",
|
|
35
|
+
"dash",
|
|
36
|
+
"video-player",
|
|
37
|
+
"zwplayer",
|
|
38
|
+
"media-player",
|
|
39
|
+
"streaming"
|
|
40
|
+
],
|
|
41
|
+
"author": "sl",
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
45
|
+
"react-router-dom": "^5.0.0 || ^6.0.0"
|
|
46
|
+
},
|
|
47
|
+
"peerDependenciesMeta": {
|
|
48
|
+
"react-router-dom": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@babel/core": "^7.23.0",
|
|
54
|
+
"@babel/preset-react": "^7.22.0",
|
|
55
|
+
"rollup": "^3.29.0",
|
|
56
|
+
"@rollup/plugin-babel": "^6.0.3",
|
|
57
|
+
"@rollup/plugin-node-resolve": "^15.2.0",
|
|
58
|
+
"rollup-plugin-peer-deps-external": "^2.2.4"
|
|
59
|
+
}
|
|
60
|
+
}
|