@ezuikit/player-theme 3.1.2-beta.2 → 3.1.2-beta.4

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/dist/constant.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /*
2
- * @ezuikit/player-theme v3.1.2-beta.2
3
- * Copyright (c) 2026-08-12 04:37:54 Ezviz-OpenBiz
2
+ * @ezuikit/player-theme v3.1.2-beta.4
3
+ * Copyright (c) 2026-08-16 15:20:04 Ezviz-OpenBiz
4
4
  * Released under the MIT License.
5
5
  */
6
6
  /**
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  /*
2
- * @ezuikit/player-theme v3.1.2-beta.2
3
- * Copyright (c) 2026-08-12 04:37:54 Ezviz-OpenBiz
2
+ * @ezuikit/player-theme v3.1.2-beta.4
3
+ * Copyright (c) 2026-08-16 15:20:04 Ezviz-OpenBiz
4
4
  * Released under the MIT License.
5
5
  */
6
6
  'use strict';
@@ -295,7 +295,7 @@ function _set_prototype_of$y(o, p) {
295
295
  function Control(options) {
296
296
  var _this;
297
297
  var _this___options;
298
- _this = EventEmitter.call(this) || this, /** 具体语言对应的值 */ _this.locale = null, _this.__options = {}, _this._disabled = false, _this._active = false, _this._camelCaseName = '';
298
+ _this = EventEmitter.call(this) || this, /** 具体语言对应的值 */ _this.locale = null, _this.__options = {}, _this._disabled = false, _this._active = false, _this._camelCaseName = '', /** click 监听引用,销毁时需显式移除 */ _this._onClickHandler = null;
299
299
  _this.__options = options;
300
300
  _this.locale = getLocale(_this.__options.locales, _this.__options.language);
301
301
  _this._camelCaseName = _this.__options.classNameSuffix ? _this.__options.classNameSuffix.replace(/[-_\s]+(.)?/g, function(_, c) {
@@ -384,6 +384,12 @@ function _set_prototype_of$y(o, p) {
384
384
  this.emit("Control." + this._camelCaseName + "Destroy");
385
385
  }
386
386
  if (((_this___options = this.__options) == null ? void 0 : _this___options.controlType) !== 'block') this.$container.removeEventListener('dblclick', this._onDBlClick);
387
+ // 移除 click 监听
388
+ if (this._onClickHandler) {
389
+ var _this_$container1;
390
+ (_this_$container1 = this.$container) == null ? void 0 : _this_$container1.removeEventListener('click', this._onClickHandler);
391
+ this._onClickHandler = null;
392
+ }
387
393
  this._active = false;
388
394
  this.removeAllListeners();
389
395
  (_this_$container = this.$container) == null ? void 0 : (_this_$container_remove = _this_$container.remove) == null ? void 0 : _this_$container_remove.call(_this_$container);
@@ -412,14 +418,16 @@ function _set_prototype_of$y(o, p) {
412
418
  * 当点击 Control 时 触发子类的 _onControlClick
413
419
  */ _proto._onClick = function _onClick() {
414
420
  var _this = this;
415
- this.$container.addEventListener('click', function(e) {
421
+ // 使用具名 handler 便于在 destroy 时精确移除,避免匿名监听残留
422
+ this._onClickHandler = function(e) {
416
423
  var _this_$container;
417
424
  // prettier-ignore
418
425
  if (!((_this_$container = _this.$container) == null ? void 0 : _this_$container.classList.contains("" + PREFIX_CLASS + "-disabled"))) {
419
426
  // _onControlClick 来源自子类中
420
427
  _this._onControlClick == null ? void 0 : _this._onControlClick.call(_this, e);
421
428
  }
422
- });
429
+ };
430
+ this.$container.addEventListener('click', this._onClickHandler);
423
431
  };
424
432
  _proto._onDBlClick = function _onDBlClick(e) {
425
433
  e.stopPropagation();
@@ -4277,6 +4285,7 @@ function debounce(func, wait) {
4277
4285
  var // rerender 画面
4278
4286
  _theme_contentControl_emit, _theme_contentControl;
4279
4287
  (_theme_contentControl = theme.contentControl) == null ? void 0 : (_theme_contentControl_emit = _theme_contentControl.emit) == null ? void 0 : _theme_contentControl_emit.call(_theme_contentControl, EVENTS.videoInfo, info.width, info.height);
4288
+ theme._videoInfo = info;
4280
4289
  }
4281
4290
  });
4282
4291
  theme == null ? void 0 : theme.on(EVENTS.resize, function() {
@@ -4608,8 +4617,14 @@ function debounce(func, wait) {
4608
4617
  }
4609
4618
  // 播放地址切换后同步 Live/RecDropdown 激活状态
4610
4619
  // changePlayUrl 后 urlInfo 已更新,firstFrameDisplay 表示新地址播放成功
4620
+ // 注意:_controlEventemitter 每次 _renderTheme 都会执行,这里的监听挂在 theme 上,
4621
+ // 必须先移除上一次的引用,避免 changeTheme 反复切换导致监听器无限累积(内存泄漏 + 重复执行)。
4622
+ if (theme._onFirstFrameSync) {
4623
+ theme.removeListener(EVENTS.firstFrameDisplay, theme._onFirstFrameSync);
4624
+ theme._onFirstFrameSync = null;
4625
+ }
4611
4626
  if (((_theme_controls11 = theme.controls) == null ? void 0 : _theme_controls11.liveControl) || ((_theme_controls12 = theme.controls) == null ? void 0 : _theme_controls12.recDropdownControl)) {
4612
- theme.on(EVENTS.firstFrameDisplay, function() {
4627
+ theme._onFirstFrameSync = function() {
4613
4628
  var _theme_controls, _theme_controls1;
4614
4629
  var urlInfo = theme.urlInfo;
4615
4630
  if ((_theme_controls = theme.controls) == null ? void 0 : _theme_controls.liveControl) {
@@ -4618,7 +4633,8 @@ function debounce(func, wait) {
4618
4633
  if ((_theme_controls1 = theme.controls) == null ? void 0 : _theme_controls1.recDropdownControl) {
4619
4634
  theme.controls.recDropdownControl._syncActiveByUrlInfo(urlInfo);
4620
4635
  }
4621
- });
4636
+ };
4637
+ theme.on(EVENTS.firstFrameDisplay, theme._onFirstFrameSync);
4622
4638
  }
4623
4639
  // 缩放控件
4624
4640
  if ((_theme_controls13 = theme.controls) == null ? void 0 : _theme_controls13.zoomControl) {
@@ -5191,15 +5207,20 @@ function _ts_generator$6(thisArg, body) {
5191
5207
  * @returns
5192
5208
  */ function getThemeDataByTemplate(theme, id) {
5193
5209
  return _async_to_generator$6(function() {
5194
- var _theme_options_token_httpToken, _theme_options_token, url;
5210
+ var _theme_options_token_httpToken, _theme_options_token, // 中止上一个在途请求,并为本次请求创建可中止的信号(destroy 时会 abort)
5211
+ _theme__templateAbortController, url, controller;
5195
5212
  return _ts_generator$6(this, function(_state) {
5196
5213
  switch(_state.label){
5197
5214
  case 0:
5198
5215
  url = theme.options.env.domain + "/api/service/media/template/getDetail?accessToken=" + (theme.options.accessToken || ((_theme_options_token = theme.options.token) == null ? void 0 : (_theme_options_token_httpToken = _theme_options_token.httpToken) == null ? void 0 : _theme_options_token_httpToken.url)) + "&id=" + id;
5216
+ (_theme__templateAbortController = theme._templateAbortController) == null ? void 0 : _theme__templateAbortController.abort();
5217
+ controller = new AbortController();
5218
+ theme._templateAbortController = controller;
5199
5219
  return [
5200
5220
  4,
5201
5221
  fetch(url, {
5202
- method: 'GET'
5222
+ method: 'GET',
5223
+ signal: controller == null ? void 0 : controller.signal
5203
5224
  }).then(function(response) {
5204
5225
  return _async_to_generator$6(function() {
5205
5226
  return _ts_generator$6(this, function(_state) {
@@ -5503,7 +5524,7 @@ function _filterLeftRightControls(btnList) {
5503
5524
  * @returns
5504
5525
  */ function getThemeData(theme, data) {
5505
5526
  return _async_to_generator$5(function() {
5506
- var themeData, _theme_logger, template;
5527
+ var themeData, _theme_logger, template, e;
5507
5528
  return _ts_generator$5(this, function(_state) {
5508
5529
  switch(_state.label){
5509
5530
  case 0:
@@ -5549,13 +5570,23 @@ function _filterLeftRightControls(btnList) {
5549
5570
  2,
5550
5571
  template
5551
5572
  ];
5573
+ // 请求返回前已销毁则静默返回,不在已释放的 theme 上派发事件
5574
+ if (theme.destroyed) return [
5575
+ 2,
5576
+ null
5577
+ ];
5552
5578
  theme.emit(EVENTS.message, theme.i18n.t('FETCH_THEME_FAILED'), 'themeError');
5553
5579
  return [
5554
5580
  2,
5555
5581
  null
5556
5582
  ];
5557
5583
  case 4:
5558
- _state.sent();
5584
+ e = _state.sent();
5585
+ // 请求被中止(destroy/重新渲染)或已销毁:静默返回
5586
+ if (theme.destroyed || (e == null ? void 0 : e.name) === 'AbortError') return [
5587
+ 2,
5588
+ null
5589
+ ];
5559
5590
  theme.emit(EVENTS.message, theme.i18n.t('FETCH_THEME_FAILED'), 'themeError');
5560
5591
  return [
5561
5592
  2,
@@ -9245,6 +9276,11 @@ var _unmountedControls = function(theme) {
9245
9276
  if (theme._onPauseTimingFunc) {
9246
9277
  theme._onPauseTimingFunc = null;
9247
9278
  }
9279
+ // 移除首帧同步监听(挂在 theme 上,需显式解绑,避免 changeTheme 累积)
9280
+ if (theme._onFirstFrameSync) {
9281
+ theme.removeListener(EVENTS.firstFrameDisplay, theme._onFirstFrameSync);
9282
+ theme._onFirstFrameSync = null;
9283
+ }
9248
9284
  if (hasControls) {
9249
9285
  theme.emit(EVENTS.control.unmountedControls);
9250
9286
  }
@@ -9515,6 +9551,10 @@ function _renderTheme(theme, data) {
9515
9551
  ];
9516
9552
  case 1:
9517
9553
  themeData = _state.sent();
9554
+ // 异步获取主题数据期间可能已 destroy(),避免在已释放的 theme 上继续渲染/emit
9555
+ if (theme.destroyed) return [
9556
+ 2
9557
+ ];
9518
9558
  _unmountedControls(theme);
9519
9559
  if (Object.prototype.toString.call(themeData) !== '[object Object]') {
9520
9560
  // 主题空
@@ -10177,7 +10217,15 @@ var THEME_DEFAULT_OPTIONS = {
10177
10217
  // _recMonthObj: Record<string, string[]> = {};
10178
10218
  /**
10179
10219
  * 录像回放的月份列表 @private
10180
- */ _this.recMonth = [], /** 清理 header/footer 动画 定时器 @private */ _this._onPauseTimingFunc = null, /** 销毁标识 @readonly */ _this.destroyed = false, _this.scaleMode = 0;
10220
+ */ _this.recMonth = [], /** 清理 header/footer 动画 定时器 @private */ _this._onPauseTimingFunc = null, /**
10221
+ * 首帧同步 Live/RecDropdown 激活态的监听器引用。
10222
+ * 每次 `_renderTheme` 重新绑定前需先移除旧引用,避免 changeTheme 累积监听。
10223
+ * @private
10224
+ */ _this._onFirstFrameSync = null, /**
10225
+ * 模板请求(getDetail)的中止控制器。
10226
+ * 重新渲染或销毁时中止在途请求,避免请求返回后在已销毁/已切换的 theme 上继续操作。
10227
+ * @private
10228
+ */ _this._templateAbortController = null, /** 销毁标识 @readonly */ _this.destroyed = false, _this.scaleMode = 0, _this._videoInfo = {};
10181
10229
  _this._initOptions(options);
10182
10230
  if (_this.options.type === 'ezopen') {
10183
10231
  var _this_urlInfo_searchParams1, _this_urlInfo1, _this_urlInfo2;
@@ -10220,7 +10268,7 @@ var THEME_DEFAULT_OPTIONS = {
10220
10268
  _this._muted = (_ref1 = (_this_options_volumeOptions_muted = (_this_options_volumeOptions1 = _this.options.volumeOptions) == null ? void 0 : _this_options_volumeOptions1.muted) != null ? _this_options_volumeOptions_muted : _this.options.muted) != null ? _ref1 : false;
10221
10269
  var _this_options_speedOptions_value, _ref2;
10222
10270
  // playbackSpeed 是 萤石标准流倍速字段
10223
- _this._speed = (_ref2 = (_this_options_speedOptions_value = (_this_options_speedOptions = _this.options.speedOptions) == null ? void 0 : _this_options_speedOptions.value) != null ? _this_options_speedOptions_value : (_this_urlInfo = _this.urlInfo) == null ? void 0 : (_this_urlInfo_searchParams = _this_urlInfo.searchParams) == null ? void 0 : _this_urlInfo_searchParams.playbackSpeed) != null ? _ref2 : 1;
10271
+ _this._speed = +((_ref2 = (_this_options_speedOptions_value = (_this_options_speedOptions = _this.options.speedOptions) == null ? void 0 : _this_options_speedOptions.value) != null ? _this_options_speedOptions_value : (_this_urlInfo = _this.urlInfo) == null ? void 0 : (_this_urlInfo_searchParams = _this_urlInfo.searchParams) == null ? void 0 : _this_urlInfo_searchParams.playbackSpeed) != null ? _ref2 : 1);
10224
10272
  _this._mobileInnerWidthHeight = _this._mobileInnerWidthHeight.bind(_this);
10225
10273
  _this._throttleMobileInnerWidthHeight = throttle(_this._mobileInnerWidthHeight, 20).bind(_this);
10226
10274
  _this._onDblClickFullscreen = debounce(_this._onDblClickFullscreen, 20).bind(_this);
@@ -10432,12 +10480,15 @@ var THEME_DEFAULT_OPTIONS = {
10432
10480
  * ```
10433
10481
  */ _proto.destroy = function destroy() {
10434
10482
  var _this = this;
10435
- var _this_contentControl, _this_$container;
10483
+ var // 中止在途的模板请求,避免其回调在已销毁的 theme 上继续执行
10484
+ _this__templateAbortController, _this_contentControl, _this_$container;
10436
10485
  if (!this.$container || this.destroyed) {
10437
10486
  // 防止多次销毁
10438
10487
  return;
10439
10488
  }
10440
10489
  this.emit(EVENTS.theme.beforeDestroy);
10490
+ (_this__templateAbortController = this._templateAbortController) == null ? void 0 : _this__templateAbortController.abort();
10491
+ this._templateAbortController = null;
10441
10492
  this._removeEventListener();
10442
10493
  _unmountedControls(this);
10443
10494
  (_this_contentControl = this.contentControl) == null ? void 0 : _this_contentControl.destroy();
@@ -10458,6 +10509,8 @@ var THEME_DEFAULT_OPTIONS = {
10458
10509
  if (this.i18n) this.i18n = null;
10459
10510
  this.recType = '';
10460
10511
  this.recMonth = []; // 清空数据
10512
+ // 重置
10513
+ this._videoInfo = {};
10461
10514
  this.emit(EVENTS.theme.destroyed);
10462
10515
  this.removeAllListeners();
10463
10516
  if (this.logger) this.logger = null;
@@ -10470,7 +10523,12 @@ var THEME_DEFAULT_OPTIONS = {
10470
10523
  /** 初始化配置项 */ _proto._initOptions = function _initOptions(options) {
10471
10524
  if (options === void 0) options = {};
10472
10525
  var _this_options_loggerOptions, _this_options_definitionOptions_list, _this_options_definitionOptions, _this_options_videoLevelList;
10473
- this.options = deepmerge(THEME_DEFAULT_OPTIONS, options, {
10526
+ // 先对模块级默认配置做一次深拷贝,得到全新的嵌套对象/数组,
10527
+ // 避免 this.options 与 THEME_DEFAULT_OPTIONS(如 mobileExtendOptions.controls、env、loggerOptions)
10528
+ // 共享引用而在多实例间相互污染。
10529
+ // 仍对用户 options 使用 clone:false —— container 可能是 DOM 节点/函数,深拷贝会破坏它。
10530
+ var defaults = deepmerge({}, THEME_DEFAULT_OPTIONS);
10531
+ this.options = deepmerge(defaults, options, {
10474
10532
  clone: false
10475
10533
  });
10476
10534
  this._url = this.options.url;
@@ -10505,10 +10563,12 @@ var THEME_DEFAULT_OPTIONS = {
10505
10563
  this.logger.log('[options] \n', this.options);
10506
10564
  }
10507
10565
  var language = this.options.language || 'zh';
10508
- var locales = deepmerge({
10566
+ // 深拷贝内置语言包,避免与模块级 zh/en 常量共享引用(I18n 若对其做合并/修改会污染全局)
10567
+ var baseLocales = deepmerge({}, {
10509
10568
  zh: zh,
10510
10569
  en: en
10511
- }, this.options.locales || {}, {
10570
+ });
10571
+ var locales = deepmerge(baseLocales, this.options.locales || {}, {
10512
10572
  clone: false
10513
10573
  });
10514
10574
  // prettier-ignore
@@ -11074,6 +11134,41 @@ var THEME_DEFAULT_OPTIONS = {
11074
11134
  return this._height;
11075
11135
  }
11076
11136
  },
11137
+ {
11138
+ key: "videoInfo",
11139
+ get: /**
11140
+ * 视频信息
11141
+ * @version 3.1.2
11142
+ * ```ts
11143
+ * theme.videoInfo // ThemeVideoInfo
11144
+ * ```
11145
+ */ function get() {
11146
+ return this._videoInfo || {};
11147
+ }
11148
+ },
11149
+ {
11150
+ key: "videoWidth",
11151
+ get: /**
11152
+ * 视频分辨率 宽
11153
+ *
11154
+ * @version 3.1.2
11155
+ *
11156
+ * ```ts
11157
+ * theme.videoWidth // number
11158
+ * ```
11159
+ */ function get() {
11160
+ return this._videoInfo.width || 0;
11161
+ }
11162
+ },
11163
+ {
11164
+ key: "videoHeight",
11165
+ get: /**
11166
+ * 视频分辨率 高
11167
+ * @version 3.1.2
11168
+ */ function get() {
11169
+ return this._videoInfo.height || 0;
11170
+ }
11171
+ },
11077
11172
  {
11078
11173
  key: "playing",
11079
11174
  get: /**
@@ -11338,13 +11433,13 @@ var THEME_DEFAULT_OPTIONS = {
11338
11433
  {
11339
11434
  key: "speed",
11340
11435
  get: function get() {
11341
- return this._speed;
11436
+ return +this._speed;
11342
11437
  },
11343
11438
  set: /**
11344
11439
  * 倍速
11345
11440
  */ function set(speed) {
11346
11441
  speed = +speed;
11347
- if (speed !== this._speed) {
11442
+ if (speed !== +this._speed) {
11348
11443
  this._speed = speed;
11349
11444
  this.emit(EVENTS.speedChange, speed);
11350
11445
  }
@@ -11491,7 +11586,7 @@ var THEME_DEFAULT_OPTIONS = {
11491
11586
  zh: zh,
11492
11587
  en: en
11493
11588
  };
11494
- /** 版本号 @since 0.0.1 */ Theme.THEME_VERSION = '3.1.2-beta.2';
11589
+ /** 版本号 @since 0.0.1 */ Theme.THEME_VERSION = '3.1.2-beta.4';
11495
11590
 
11496
11591
  exports.Control = Control;
11497
11592
  exports.EVENTS = EVENTS;
package/dist/index.esm.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /*
2
- * @ezuikit/player-theme v3.1.2-beta.2
3
- * Copyright (c) 2026-08-12 04:37:54 Ezviz-OpenBiz
2
+ * @ezuikit/player-theme v3.1.2-beta.4
3
+ * Copyright (c) 2026-08-16 15:20:04 Ezviz-OpenBiz
4
4
  * Released under the MIT License.
5
5
  */
6
6
  import EventEmitter from 'eventemitter3';
@@ -293,7 +293,7 @@ function _set_prototype_of$y(o, p) {
293
293
  function Control(options) {
294
294
  var _this;
295
295
  var _this___options;
296
- _this = EventEmitter.call(this) || this, /** 具体语言对应的值 */ _this.locale = null, _this.__options = {}, _this._disabled = false, _this._active = false, _this._camelCaseName = '';
296
+ _this = EventEmitter.call(this) || this, /** 具体语言对应的值 */ _this.locale = null, _this.__options = {}, _this._disabled = false, _this._active = false, _this._camelCaseName = '', /** click 监听引用,销毁时需显式移除 */ _this._onClickHandler = null;
297
297
  _this.__options = options;
298
298
  _this.locale = getLocale(_this.__options.locales, _this.__options.language);
299
299
  _this._camelCaseName = _this.__options.classNameSuffix ? _this.__options.classNameSuffix.replace(/[-_\s]+(.)?/g, function(_, c) {
@@ -382,6 +382,12 @@ function _set_prototype_of$y(o, p) {
382
382
  this.emit("Control." + this._camelCaseName + "Destroy");
383
383
  }
384
384
  if (((_this___options = this.__options) == null ? void 0 : _this___options.controlType) !== 'block') this.$container.removeEventListener('dblclick', this._onDBlClick);
385
+ // 移除 click 监听
386
+ if (this._onClickHandler) {
387
+ var _this_$container1;
388
+ (_this_$container1 = this.$container) == null ? void 0 : _this_$container1.removeEventListener('click', this._onClickHandler);
389
+ this._onClickHandler = null;
390
+ }
385
391
  this._active = false;
386
392
  this.removeAllListeners();
387
393
  (_this_$container = this.$container) == null ? void 0 : (_this_$container_remove = _this_$container.remove) == null ? void 0 : _this_$container_remove.call(_this_$container);
@@ -410,14 +416,16 @@ function _set_prototype_of$y(o, p) {
410
416
  * 当点击 Control 时 触发子类的 _onControlClick
411
417
  */ _proto._onClick = function _onClick() {
412
418
  var _this = this;
413
- this.$container.addEventListener('click', function(e) {
419
+ // 使用具名 handler 便于在 destroy 时精确移除,避免匿名监听残留
420
+ this._onClickHandler = function(e) {
414
421
  var _this_$container;
415
422
  // prettier-ignore
416
423
  if (!((_this_$container = _this.$container) == null ? void 0 : _this_$container.classList.contains("" + PREFIX_CLASS + "-disabled"))) {
417
424
  // _onControlClick 来源自子类中
418
425
  _this._onControlClick == null ? void 0 : _this._onControlClick.call(_this, e);
419
426
  }
420
- });
427
+ };
428
+ this.$container.addEventListener('click', this._onClickHandler);
421
429
  };
422
430
  _proto._onDBlClick = function _onDBlClick(e) {
423
431
  e.stopPropagation();
@@ -4275,6 +4283,7 @@ function debounce(func, wait) {
4275
4283
  var // rerender 画面
4276
4284
  _theme_contentControl_emit, _theme_contentControl;
4277
4285
  (_theme_contentControl = theme.contentControl) == null ? void 0 : (_theme_contentControl_emit = _theme_contentControl.emit) == null ? void 0 : _theme_contentControl_emit.call(_theme_contentControl, EVENTS.videoInfo, info.width, info.height);
4286
+ theme._videoInfo = info;
4278
4287
  }
4279
4288
  });
4280
4289
  theme == null ? void 0 : theme.on(EVENTS.resize, function() {
@@ -4606,8 +4615,14 @@ function debounce(func, wait) {
4606
4615
  }
4607
4616
  // 播放地址切换后同步 Live/RecDropdown 激活状态
4608
4617
  // changePlayUrl 后 urlInfo 已更新,firstFrameDisplay 表示新地址播放成功
4618
+ // 注意:_controlEventemitter 每次 _renderTheme 都会执行,这里的监听挂在 theme 上,
4619
+ // 必须先移除上一次的引用,避免 changeTheme 反复切换导致监听器无限累积(内存泄漏 + 重复执行)。
4620
+ if (theme._onFirstFrameSync) {
4621
+ theme.removeListener(EVENTS.firstFrameDisplay, theme._onFirstFrameSync);
4622
+ theme._onFirstFrameSync = null;
4623
+ }
4609
4624
  if (((_theme_controls11 = theme.controls) == null ? void 0 : _theme_controls11.liveControl) || ((_theme_controls12 = theme.controls) == null ? void 0 : _theme_controls12.recDropdownControl)) {
4610
- theme.on(EVENTS.firstFrameDisplay, function() {
4625
+ theme._onFirstFrameSync = function() {
4611
4626
  var _theme_controls, _theme_controls1;
4612
4627
  var urlInfo = theme.urlInfo;
4613
4628
  if ((_theme_controls = theme.controls) == null ? void 0 : _theme_controls.liveControl) {
@@ -4616,7 +4631,8 @@ function debounce(func, wait) {
4616
4631
  if ((_theme_controls1 = theme.controls) == null ? void 0 : _theme_controls1.recDropdownControl) {
4617
4632
  theme.controls.recDropdownControl._syncActiveByUrlInfo(urlInfo);
4618
4633
  }
4619
- });
4634
+ };
4635
+ theme.on(EVENTS.firstFrameDisplay, theme._onFirstFrameSync);
4620
4636
  }
4621
4637
  // 缩放控件
4622
4638
  if ((_theme_controls13 = theme.controls) == null ? void 0 : _theme_controls13.zoomControl) {
@@ -5189,15 +5205,20 @@ function _ts_generator$6(thisArg, body) {
5189
5205
  * @returns
5190
5206
  */ function getThemeDataByTemplate(theme, id) {
5191
5207
  return _async_to_generator$6(function() {
5192
- var _theme_options_token_httpToken, _theme_options_token, url;
5208
+ var _theme_options_token_httpToken, _theme_options_token, // 中止上一个在途请求,并为本次请求创建可中止的信号(destroy 时会 abort)
5209
+ _theme__templateAbortController, url, controller;
5193
5210
  return _ts_generator$6(this, function(_state) {
5194
5211
  switch(_state.label){
5195
5212
  case 0:
5196
5213
  url = theme.options.env.domain + "/api/service/media/template/getDetail?accessToken=" + (theme.options.accessToken || ((_theme_options_token = theme.options.token) == null ? void 0 : (_theme_options_token_httpToken = _theme_options_token.httpToken) == null ? void 0 : _theme_options_token_httpToken.url)) + "&id=" + id;
5214
+ (_theme__templateAbortController = theme._templateAbortController) == null ? void 0 : _theme__templateAbortController.abort();
5215
+ controller = new AbortController();
5216
+ theme._templateAbortController = controller;
5197
5217
  return [
5198
5218
  4,
5199
5219
  fetch(url, {
5200
- method: 'GET'
5220
+ method: 'GET',
5221
+ signal: controller == null ? void 0 : controller.signal
5201
5222
  }).then(function(response) {
5202
5223
  return _async_to_generator$6(function() {
5203
5224
  return _ts_generator$6(this, function(_state) {
@@ -5501,7 +5522,7 @@ function _filterLeftRightControls(btnList) {
5501
5522
  * @returns
5502
5523
  */ function getThemeData(theme, data) {
5503
5524
  return _async_to_generator$5(function() {
5504
- var themeData, _theme_logger, template;
5525
+ var themeData, _theme_logger, template, e;
5505
5526
  return _ts_generator$5(this, function(_state) {
5506
5527
  switch(_state.label){
5507
5528
  case 0:
@@ -5547,13 +5568,23 @@ function _filterLeftRightControls(btnList) {
5547
5568
  2,
5548
5569
  template
5549
5570
  ];
5571
+ // 请求返回前已销毁则静默返回,不在已释放的 theme 上派发事件
5572
+ if (theme.destroyed) return [
5573
+ 2,
5574
+ null
5575
+ ];
5550
5576
  theme.emit(EVENTS.message, theme.i18n.t('FETCH_THEME_FAILED'), 'themeError');
5551
5577
  return [
5552
5578
  2,
5553
5579
  null
5554
5580
  ];
5555
5581
  case 4:
5556
- _state.sent();
5582
+ e = _state.sent();
5583
+ // 请求被中止(destroy/重新渲染)或已销毁:静默返回
5584
+ if (theme.destroyed || (e == null ? void 0 : e.name) === 'AbortError') return [
5585
+ 2,
5586
+ null
5587
+ ];
5557
5588
  theme.emit(EVENTS.message, theme.i18n.t('FETCH_THEME_FAILED'), 'themeError');
5558
5589
  return [
5559
5590
  2,
@@ -9243,6 +9274,11 @@ var _unmountedControls = function(theme) {
9243
9274
  if (theme._onPauseTimingFunc) {
9244
9275
  theme._onPauseTimingFunc = null;
9245
9276
  }
9277
+ // 移除首帧同步监听(挂在 theme 上,需显式解绑,避免 changeTheme 累积)
9278
+ if (theme._onFirstFrameSync) {
9279
+ theme.removeListener(EVENTS.firstFrameDisplay, theme._onFirstFrameSync);
9280
+ theme._onFirstFrameSync = null;
9281
+ }
9246
9282
  if (hasControls) {
9247
9283
  theme.emit(EVENTS.control.unmountedControls);
9248
9284
  }
@@ -9513,6 +9549,10 @@ function _renderTheme(theme, data) {
9513
9549
  ];
9514
9550
  case 1:
9515
9551
  themeData = _state.sent();
9552
+ // 异步获取主题数据期间可能已 destroy(),避免在已释放的 theme 上继续渲染/emit
9553
+ if (theme.destroyed) return [
9554
+ 2
9555
+ ];
9516
9556
  _unmountedControls(theme);
9517
9557
  if (Object.prototype.toString.call(themeData) !== '[object Object]') {
9518
9558
  // 主题空
@@ -10175,7 +10215,15 @@ var THEME_DEFAULT_OPTIONS = {
10175
10215
  // _recMonthObj: Record<string, string[]> = {};
10176
10216
  /**
10177
10217
  * 录像回放的月份列表 @private
10178
- */ _this.recMonth = [], /** 清理 header/footer 动画 定时器 @private */ _this._onPauseTimingFunc = null, /** 销毁标识 @readonly */ _this.destroyed = false, _this.scaleMode = 0;
10218
+ */ _this.recMonth = [], /** 清理 header/footer 动画 定时器 @private */ _this._onPauseTimingFunc = null, /**
10219
+ * 首帧同步 Live/RecDropdown 激活态的监听器引用。
10220
+ * 每次 `_renderTheme` 重新绑定前需先移除旧引用,避免 changeTheme 累积监听。
10221
+ * @private
10222
+ */ _this._onFirstFrameSync = null, /**
10223
+ * 模板请求(getDetail)的中止控制器。
10224
+ * 重新渲染或销毁时中止在途请求,避免请求返回后在已销毁/已切换的 theme 上继续操作。
10225
+ * @private
10226
+ */ _this._templateAbortController = null, /** 销毁标识 @readonly */ _this.destroyed = false, _this.scaleMode = 0, _this._videoInfo = {};
10179
10227
  _this._initOptions(options);
10180
10228
  if (_this.options.type === 'ezopen') {
10181
10229
  var _this_urlInfo_searchParams1, _this_urlInfo1, _this_urlInfo2;
@@ -10218,7 +10266,7 @@ var THEME_DEFAULT_OPTIONS = {
10218
10266
  _this._muted = (_ref1 = (_this_options_volumeOptions_muted = (_this_options_volumeOptions1 = _this.options.volumeOptions) == null ? void 0 : _this_options_volumeOptions1.muted) != null ? _this_options_volumeOptions_muted : _this.options.muted) != null ? _ref1 : false;
10219
10267
  var _this_options_speedOptions_value, _ref2;
10220
10268
  // playbackSpeed 是 萤石标准流倍速字段
10221
- _this._speed = (_ref2 = (_this_options_speedOptions_value = (_this_options_speedOptions = _this.options.speedOptions) == null ? void 0 : _this_options_speedOptions.value) != null ? _this_options_speedOptions_value : (_this_urlInfo = _this.urlInfo) == null ? void 0 : (_this_urlInfo_searchParams = _this_urlInfo.searchParams) == null ? void 0 : _this_urlInfo_searchParams.playbackSpeed) != null ? _ref2 : 1;
10269
+ _this._speed = +((_ref2 = (_this_options_speedOptions_value = (_this_options_speedOptions = _this.options.speedOptions) == null ? void 0 : _this_options_speedOptions.value) != null ? _this_options_speedOptions_value : (_this_urlInfo = _this.urlInfo) == null ? void 0 : (_this_urlInfo_searchParams = _this_urlInfo.searchParams) == null ? void 0 : _this_urlInfo_searchParams.playbackSpeed) != null ? _ref2 : 1);
10222
10270
  _this._mobileInnerWidthHeight = _this._mobileInnerWidthHeight.bind(_this);
10223
10271
  _this._throttleMobileInnerWidthHeight = throttle(_this._mobileInnerWidthHeight, 20).bind(_this);
10224
10272
  _this._onDblClickFullscreen = debounce(_this._onDblClickFullscreen, 20).bind(_this);
@@ -10430,12 +10478,15 @@ var THEME_DEFAULT_OPTIONS = {
10430
10478
  * ```
10431
10479
  */ _proto.destroy = function destroy() {
10432
10480
  var _this = this;
10433
- var _this_contentControl, _this_$container;
10481
+ var // 中止在途的模板请求,避免其回调在已销毁的 theme 上继续执行
10482
+ _this__templateAbortController, _this_contentControl, _this_$container;
10434
10483
  if (!this.$container || this.destroyed) {
10435
10484
  // 防止多次销毁
10436
10485
  return;
10437
10486
  }
10438
10487
  this.emit(EVENTS.theme.beforeDestroy);
10488
+ (_this__templateAbortController = this._templateAbortController) == null ? void 0 : _this__templateAbortController.abort();
10489
+ this._templateAbortController = null;
10439
10490
  this._removeEventListener();
10440
10491
  _unmountedControls(this);
10441
10492
  (_this_contentControl = this.contentControl) == null ? void 0 : _this_contentControl.destroy();
@@ -10456,6 +10507,8 @@ var THEME_DEFAULT_OPTIONS = {
10456
10507
  if (this.i18n) this.i18n = null;
10457
10508
  this.recType = '';
10458
10509
  this.recMonth = []; // 清空数据
10510
+ // 重置
10511
+ this._videoInfo = {};
10459
10512
  this.emit(EVENTS.theme.destroyed);
10460
10513
  this.removeAllListeners();
10461
10514
  if (this.logger) this.logger = null;
@@ -10468,7 +10521,12 @@ var THEME_DEFAULT_OPTIONS = {
10468
10521
  /** 初始化配置项 */ _proto._initOptions = function _initOptions(options) {
10469
10522
  if (options === void 0) options = {};
10470
10523
  var _this_options_loggerOptions, _this_options_definitionOptions_list, _this_options_definitionOptions, _this_options_videoLevelList;
10471
- this.options = deepmerge(THEME_DEFAULT_OPTIONS, options, {
10524
+ // 先对模块级默认配置做一次深拷贝,得到全新的嵌套对象/数组,
10525
+ // 避免 this.options 与 THEME_DEFAULT_OPTIONS(如 mobileExtendOptions.controls、env、loggerOptions)
10526
+ // 共享引用而在多实例间相互污染。
10527
+ // 仍对用户 options 使用 clone:false —— container 可能是 DOM 节点/函数,深拷贝会破坏它。
10528
+ var defaults = deepmerge({}, THEME_DEFAULT_OPTIONS);
10529
+ this.options = deepmerge(defaults, options, {
10472
10530
  clone: false
10473
10531
  });
10474
10532
  this._url = this.options.url;
@@ -10503,10 +10561,12 @@ var THEME_DEFAULT_OPTIONS = {
10503
10561
  this.logger.log('[options] \n', this.options);
10504
10562
  }
10505
10563
  var language = this.options.language || 'zh';
10506
- var locales = deepmerge({
10564
+ // 深拷贝内置语言包,避免与模块级 zh/en 常量共享引用(I18n 若对其做合并/修改会污染全局)
10565
+ var baseLocales = deepmerge({}, {
10507
10566
  zh: zh,
10508
10567
  en: en
10509
- }, this.options.locales || {}, {
10568
+ });
10569
+ var locales = deepmerge(baseLocales, this.options.locales || {}, {
10510
10570
  clone: false
10511
10571
  });
10512
10572
  // prettier-ignore
@@ -11072,6 +11132,41 @@ var THEME_DEFAULT_OPTIONS = {
11072
11132
  return this._height;
11073
11133
  }
11074
11134
  },
11135
+ {
11136
+ key: "videoInfo",
11137
+ get: /**
11138
+ * 视频信息
11139
+ * @version 3.1.2
11140
+ * ```ts
11141
+ * theme.videoInfo // ThemeVideoInfo
11142
+ * ```
11143
+ */ function get() {
11144
+ return this._videoInfo || {};
11145
+ }
11146
+ },
11147
+ {
11148
+ key: "videoWidth",
11149
+ get: /**
11150
+ * 视频分辨率 宽
11151
+ *
11152
+ * @version 3.1.2
11153
+ *
11154
+ * ```ts
11155
+ * theme.videoWidth // number
11156
+ * ```
11157
+ */ function get() {
11158
+ return this._videoInfo.width || 0;
11159
+ }
11160
+ },
11161
+ {
11162
+ key: "videoHeight",
11163
+ get: /**
11164
+ * 视频分辨率 高
11165
+ * @version 3.1.2
11166
+ */ function get() {
11167
+ return this._videoInfo.height || 0;
11168
+ }
11169
+ },
11075
11170
  {
11076
11171
  key: "playing",
11077
11172
  get: /**
@@ -11336,13 +11431,13 @@ var THEME_DEFAULT_OPTIONS = {
11336
11431
  {
11337
11432
  key: "speed",
11338
11433
  get: function get() {
11339
- return this._speed;
11434
+ return +this._speed;
11340
11435
  },
11341
11436
  set: /**
11342
11437
  * 倍速
11343
11438
  */ function set(speed) {
11344
11439
  speed = +speed;
11345
- if (speed !== this._speed) {
11440
+ if (speed !== +this._speed) {
11346
11441
  this._speed = speed;
11347
11442
  this.emit(EVENTS.speedChange, speed);
11348
11443
  }
@@ -11489,6 +11584,6 @@ var THEME_DEFAULT_OPTIONS = {
11489
11584
  zh: zh,
11490
11585
  en: en
11491
11586
  };
11492
- /** 版本号 @since 0.0.1 */ Theme.THEME_VERSION = '3.1.2-beta.2';
11587
+ /** 版本号 @since 0.0.1 */ Theme.THEME_VERSION = '3.1.2-beta.4';
11493
11588
 
11494
11589
  export { Control, EVENTS, Fullscreen, Loading, Message, Play, Poster, Rec, Theme, Utils, Volume };