@galacean/effects-plugin-multimedia 2.9.0-alpha.2 → 2.9.1-beta.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/dist/index.js +525 -174
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +2 -2
- package/dist/index.min.js.map +1 -1
- package/dist/index.mjs +526 -175
- package/dist/index.mjs.map +1 -1
- package/dist/video/video-component.d.ts +175 -31
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
* Description: Galacean Effects player multimedia plugin
|
|
4
4
|
* Author: Ant Group CO., Ltd.
|
|
5
5
|
* Contributors: 云垣
|
|
6
|
-
* Version: v2.9.
|
|
6
|
+
* Version: v2.9.1-beta.0
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import * as EFFECTS from '@galacean/effects';
|
|
10
|
-
import { isFunction, spec, passRenderLevel, loadVideo, loadBinary, Plugin, effectsClass, math,
|
|
10
|
+
import { isFunction, spec, passRenderLevel, loadVideo, loadBinary, Plugin, effectsClass, math, MaskableGraphic, Texture, RendererComponent, registerPlugin, logger } from '@galacean/effects';
|
|
11
11
|
|
|
12
12
|
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
|
|
13
13
|
try {
|
|
@@ -557,25 +557,50 @@ var VideoComponent = /*#__PURE__*/ function(MaskableGraphic) {
|
|
|
557
557
|
var _this;
|
|
558
558
|
_this = MaskableGraphic.call(this, engine) || this;
|
|
559
559
|
/**
|
|
560
|
-
* 播放标志位
|
|
561
|
-
*/ _this.played = false;
|
|
562
|
-
_this.pendingPause = false;
|
|
563
|
-
_this.threshold = 0.03;
|
|
564
|
-
/**
|
|
565
|
-
* 解决 video 暂停报错问题
|
|
566
|
-
*
|
|
567
|
-
* video.play(); // <-- This is asynchronous!
|
|
568
|
-
*
|
|
569
|
-
* video.pause();
|
|
570
|
-
*
|
|
571
|
-
* @see https://developer.chrome.com/blog/play-request-was-interrupted
|
|
572
|
-
*/ _this.isPlayLoading = false;
|
|
573
|
-
/**
|
|
574
560
|
* 视频元素是否激活
|
|
575
561
|
*/ _this.isVideoActive = false;
|
|
576
562
|
/**
|
|
577
563
|
* 是否为透明视频
|
|
578
564
|
*/ _this.transparent = false;
|
|
565
|
+
/**
|
|
566
|
+
* 是否由用户手动控制播放速率(覆盖合成的播放速率)
|
|
567
|
+
*/ _this.manualPlaybackRate = false;
|
|
568
|
+
/**
|
|
569
|
+
* 是否由用户手动控制循环播放(覆盖合成的结束行为)
|
|
570
|
+
*/ _this.manualLoop = false;
|
|
571
|
+
/**
|
|
572
|
+
* 是否由用户手动暂停视频
|
|
573
|
+
*/ _this.manualPause = false;
|
|
574
|
+
/**
|
|
575
|
+
* 视频是否已开始播放
|
|
576
|
+
*/ _this.playTriggered = false;
|
|
577
|
+
/**
|
|
578
|
+
* 上一次的视频时间,用于检测重播
|
|
579
|
+
*/ _this.lastVideoTime = -1;
|
|
580
|
+
/**
|
|
581
|
+
* 视频是否已经销毁(用于 destroy 结束行为,确保只重置一次)
|
|
582
|
+
*/ _this.videoDestroyed = false;
|
|
583
|
+
/**
|
|
584
|
+
* 视频是否处于 seek 中
|
|
585
|
+
* seek 期间禁止上传帧,避免 destroy 后 seek 回 0 期间渲染旧帧
|
|
586
|
+
*/ _this.videoSeeking = false;
|
|
587
|
+
/**
|
|
588
|
+
* 待执行的 seek 目标时间,延迟到 onUpdate 中处理以避免竞态。
|
|
589
|
+
* 值为 null 表示没有待执行的 seek。
|
|
590
|
+
*/ _this.pendingSeekTime = null;
|
|
591
|
+
/**
|
|
592
|
+
* 是否正在处理 gotoAndStop 的 seek
|
|
593
|
+
* 用于跳过 pause 事件触发的 pauseVideoElement,等 seek 完成后再暂停
|
|
594
|
+
*/ _this.isGotoAndStopSeeking = false;
|
|
595
|
+
/**
|
|
596
|
+
* 是否刚收到 goto 事件,等待后续 play/pause 事件来区分场景
|
|
597
|
+
*/ _this.isWaitingForGotoResult = false;
|
|
598
|
+
/**
|
|
599
|
+
* 当前正在执行的 play() Promise,用于串行化 play 调用,避免上的竞态
|
|
600
|
+
*/ _this.playPromise = null;
|
|
601
|
+
/**
|
|
602
|
+
* 存储事件监听器的移除函数,用于销毁时清理
|
|
603
|
+
*/ _this.eventDisposers = [];
|
|
579
604
|
_this.name = "MVideo" + seed++;
|
|
580
605
|
return _this;
|
|
581
606
|
}
|
|
@@ -589,6 +614,11 @@ var VideoComponent = /*#__PURE__*/ function(MaskableGraphic) {
|
|
|
589
614
|
case 0:
|
|
590
615
|
oldTexture = _this.renderer.texture;
|
|
591
616
|
composition = _this.item.composition;
|
|
617
|
+
if (!composition) {
|
|
618
|
+
return [
|
|
619
|
+
2
|
|
620
|
+
];
|
|
621
|
+
}
|
|
592
622
|
if (!(typeof input === "string")) return [
|
|
593
623
|
3,
|
|
594
624
|
2
|
|
@@ -607,11 +637,6 @@ var VideoComponent = /*#__PURE__*/ function(MaskableGraphic) {
|
|
|
607
637
|
texture = input;
|
|
608
638
|
_state.label = 3;
|
|
609
639
|
case 3:
|
|
610
|
-
if (!composition) {
|
|
611
|
-
return [
|
|
612
|
-
2
|
|
613
|
-
];
|
|
614
|
-
}
|
|
615
640
|
composition.textures.forEach(function(cachedTexture, index) {
|
|
616
641
|
if (cachedTexture === oldTexture) {
|
|
617
642
|
composition.textures[index] = texture;
|
|
@@ -635,21 +660,15 @@ var VideoComponent = /*#__PURE__*/ function(MaskableGraphic) {
|
|
|
635
660
|
if (!composition) {
|
|
636
661
|
return;
|
|
637
662
|
}
|
|
638
|
-
this.
|
|
639
|
-
_this.
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
}
|
|
648
|
-
_this.playVideo();
|
|
649
|
-
};
|
|
650
|
-
composition.on("goto", this.handleGoto);
|
|
651
|
-
composition.on("pause", this.handlePause);
|
|
652
|
-
composition.on("play", this.handlePlay);
|
|
663
|
+
this.eventDisposers.push(composition.on("goto", function() {
|
|
664
|
+
return _this.handleGoto();
|
|
665
|
+
}), composition.on("play", function() {
|
|
666
|
+
return _this.handleCompositionPlay();
|
|
667
|
+
}), composition.on("pause", function() {
|
|
668
|
+
return _this.handleCompositionPause();
|
|
669
|
+
}), composition.on("end", function() {
|
|
670
|
+
return _this.handleCompositionEnd();
|
|
671
|
+
}));
|
|
653
672
|
};
|
|
654
673
|
_proto.fromData = function fromData(data) {
|
|
655
674
|
MaskableGraphic.prototype.fromData.call(this, data);
|
|
@@ -665,94 +684,504 @@ var VideoComponent = /*#__PURE__*/ function(MaskableGraphic) {
|
|
|
665
684
|
var videoAsset = this.engine.findObject(video);
|
|
666
685
|
if (videoAsset) {
|
|
667
686
|
this.video = videoAsset.data;
|
|
668
|
-
this.
|
|
687
|
+
this.video.playbackRate = playbackRate;
|
|
669
688
|
this.setVolume(volume);
|
|
670
689
|
this.setMuted(muted);
|
|
671
690
|
var endBehavior = this.item.definition.endBehavior;
|
|
672
|
-
// 如果元素设置为 destroy
|
|
673
691
|
if (endBehavior === spec.EndBehavior.destroy || endBehavior === spec.EndBehavior.freeze) {
|
|
674
|
-
this.
|
|
692
|
+
this.video.loop = false;
|
|
675
693
|
} else if (endBehavior === spec.EndBehavior.restart) {
|
|
676
|
-
this.
|
|
694
|
+
this.video.loop = true;
|
|
677
695
|
}
|
|
678
696
|
}
|
|
679
697
|
}
|
|
680
698
|
this.interaction = interaction;
|
|
681
|
-
this.pauseVideo();
|
|
682
699
|
if (this.transparent) {
|
|
683
700
|
this.material.enableMacro("TRANSPARENT_VIDEO", this.transparent);
|
|
684
701
|
}
|
|
685
702
|
this.material.setColor("_Color", new math.Color().setFromArray(startColor));
|
|
686
703
|
};
|
|
687
|
-
_proto.render = function render(renderer) {
|
|
688
|
-
MaskableGraphic.prototype.render.call(this, renderer);
|
|
689
|
-
this.renderer.texture.uploadCurrentVideoFrame();
|
|
690
|
-
};
|
|
691
704
|
_proto.onUpdate = function onUpdate(dt) {
|
|
692
705
|
MaskableGraphic.prototype.onUpdate.call(this, dt);
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
//
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
706
|
+
if (!this.video || this.video.readyState < 2) {
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
// 处理延迟 seek(避免与 forwardTime 同步调用产生竞态)
|
|
710
|
+
if (this.processPendingSeek()) {
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
// 检测当前是否为重播状态
|
|
714
|
+
this.detectCompositionRestart();
|
|
715
|
+
// 根据结束行为决定视频状态
|
|
716
|
+
if (this.shouldFreezeVideo()) {
|
|
717
|
+
this.freezeVideo();
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
if (this.shouldStartVideo()) {
|
|
721
|
+
this.startVideo();
|
|
722
|
+
}
|
|
723
|
+
this.updatePlaybackRate();
|
|
724
|
+
this.ensureLoopFlag();
|
|
725
|
+
this.handleDestroyBehavior();
|
|
726
|
+
// 上传当前视频帧
|
|
727
|
+
if (!this.videoSeeking) {
|
|
728
|
+
this.renderer.texture.uploadCurrentVideoFrame();
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
_proto.onDestroy = function onDestroy() {
|
|
732
|
+
this.eventDisposers.forEach(function(dispose) {
|
|
733
|
+
return dispose();
|
|
734
|
+
});
|
|
735
|
+
this.eventDisposers = [];
|
|
736
|
+
MaskableGraphic.prototype.onDestroy.call(this);
|
|
737
|
+
this.playTriggered = false;
|
|
738
|
+
this.playPromise = null;
|
|
739
|
+
if (this.video) {
|
|
740
|
+
this.video.pause();
|
|
741
|
+
this.video.src = "";
|
|
742
|
+
this.video.load();
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
_proto.onDisable = function onDisable() {
|
|
746
|
+
MaskableGraphic.prototype.onDisable.call(this);
|
|
747
|
+
this.isVideoActive = false;
|
|
748
|
+
this.playTriggered = false;
|
|
749
|
+
this.pauseVideoElement();
|
|
750
|
+
};
|
|
751
|
+
_proto.onEnable = function onEnable() {
|
|
752
|
+
MaskableGraphic.prototype.onEnable.call(this);
|
|
753
|
+
this.isVideoActive = true;
|
|
754
|
+
this.playTriggered = false;
|
|
755
|
+
this.syncVideoToItemTime();
|
|
756
|
+
};
|
|
757
|
+
/**
|
|
758
|
+
* 处理 goto 事件:重置播放状态,记录待 seek 时间
|
|
759
|
+
*/ _proto.handleGoto = function handleGoto() {
|
|
760
|
+
// 通过后续事件区分 gotoAndPlay 和 gotoAndStop
|
|
761
|
+
this.isWaitingForGotoResult = true;
|
|
762
|
+
this.playTriggered = false;
|
|
763
|
+
this.manualPause = false;
|
|
764
|
+
this.pendingSeekTime = this.getItemSeekTime();
|
|
765
|
+
};
|
|
766
|
+
/**
|
|
767
|
+
* 处理合成 pause 事件
|
|
768
|
+
* 如果刚收到 goto 事件且等待结果,说明是 gotoAndStop 场景
|
|
769
|
+
*/ _proto.handleCompositionPause = function handleCompositionPause() {
|
|
770
|
+
// gotoAndStop 场景
|
|
771
|
+
if (this.isWaitingForGotoResult) {
|
|
772
|
+
this.isWaitingForGotoResult = false;
|
|
773
|
+
if (this.video && this.video.readyState >= 2) {
|
|
774
|
+
this.isGotoAndStopSeeking = true;
|
|
775
|
+
this.performSeek(this.getItemSeekTime(), false, true);
|
|
776
|
+
}
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
// 普通 pause 事件:暂停视频
|
|
780
|
+
this.pauseVideoElement();
|
|
781
|
+
};
|
|
782
|
+
/**
|
|
783
|
+
* 处理合成 play 事件(合成开始/重播/恢复时触发)
|
|
784
|
+
*/ _proto.handleCompositionPlay = function handleCompositionPlay() {
|
|
785
|
+
// 如果正在等待 goto 结果,说明是 gotoAndPlay 场景
|
|
786
|
+
if (this.isWaitingForGotoResult) {
|
|
787
|
+
this.isWaitingForGotoResult = false;
|
|
788
|
+
}
|
|
789
|
+
// 合成未结束时(暂停后恢复),恢复视频播放
|
|
790
|
+
if (!this.checkCompositionEnded()) {
|
|
791
|
+
var _this_video;
|
|
792
|
+
// 如果正在 seeking,不恢复播放,等 seek 完成后再恢复
|
|
793
|
+
if (this.canPlayCurrentItem() && ((_this_video = this.video) == null ? void 0 : _this_video.paused)) {
|
|
794
|
+
this.safePlay();
|
|
795
|
+
}
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
this.playTriggered = false;
|
|
799
|
+
this.manualPause = false;
|
|
800
|
+
var videoEndBehavior = this.item.endBehavior;
|
|
801
|
+
if (videoEndBehavior === spec.EndBehavior.freeze && this.video) {
|
|
802
|
+
this.pendingSeekTime = 0;
|
|
803
|
+
} else if (videoEndBehavior === spec.EndBehavior.destroy) {
|
|
804
|
+
this.videoDestroyed = false;
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
/**
|
|
808
|
+
* 根据合成结束行为决定视频的后续处理
|
|
809
|
+
*/ _proto.handleCompositionEnd = function handleCompositionEnd() {
|
|
810
|
+
var _this_item_composition;
|
|
811
|
+
var rootEndBehavior = (_this_item_composition = this.item.composition) == null ? void 0 : _this_item_composition.rootItem.endBehavior;
|
|
812
|
+
if (rootEndBehavior === spec.EndBehavior.restart) {
|
|
813
|
+
// 合成 restart:所有视频都需要 seek 回 0,和合成时间对齐
|
|
814
|
+
if (!this.videoSeeking) {
|
|
815
|
+
this.pendingSeekTime = 0;
|
|
816
|
+
}
|
|
817
|
+
this.playTriggered = false;
|
|
818
|
+
this.videoDestroyed = false;
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
if (rootEndBehavior === spec.EndBehavior.forward) {
|
|
822
|
+
// forward:合成时间继续往前走,视频也继续播
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
// freeze / destroy:合成真正结束,暂停视频
|
|
826
|
+
this.playTriggered = false;
|
|
827
|
+
this.pauseVideoElement();
|
|
828
|
+
};
|
|
829
|
+
/**
|
|
830
|
+
* 视频是否已播放到末尾
|
|
831
|
+
*/ _proto.checkVideoEnded = function checkVideoEnded() {
|
|
832
|
+
var videoEndBehavior = this.item.endBehavior;
|
|
833
|
+
// restart 行为的视频永远不会"结束"(由浏览器原生 loop 处理)
|
|
834
|
+
if (videoEndBehavior === spec.EndBehavior.restart) {
|
|
835
|
+
return false;
|
|
836
|
+
}
|
|
837
|
+
return this.video.currentTime + VideoComponent.threshold >= this.video.duration;
|
|
838
|
+
};
|
|
839
|
+
/**
|
|
840
|
+
* 合成是否已到达结束时间
|
|
841
|
+
*/ _proto.checkCompositionEnded = function checkCompositionEnded() {
|
|
842
|
+
var composition = this.item.composition;
|
|
843
|
+
if (!composition) {
|
|
844
|
+
return false;
|
|
845
|
+
}
|
|
846
|
+
return composition.time + VideoComponent.threshold >= composition.rootItem.duration;
|
|
847
|
+
};
|
|
848
|
+
/**
|
|
849
|
+
* 是否应该冻结视频(暂停在当前帧)
|
|
850
|
+
*/ _proto.shouldFreezeVideo = function shouldFreezeVideo() {
|
|
851
|
+
var _this_item_composition;
|
|
852
|
+
var isVideoEnded = this.checkVideoEnded();
|
|
853
|
+
var isCompositionEnded = this.checkCompositionEnded();
|
|
854
|
+
var videoEndBehavior = this.item.endBehavior;
|
|
855
|
+
var rootEndBehavior = (_this_item_composition = this.item.composition) == null ? void 0 : _this_item_composition.rootItem.endBehavior;
|
|
856
|
+
// 合成结束且合成行为是 freeze 时冻结视频
|
|
857
|
+
var isCompositionFrozen = isCompositionEnded && rootEndBehavior === spec.EndBehavior.freeze;
|
|
858
|
+
// 合成结束且视频行为是 freeze,除合成 restart 外均冻结视频
|
|
859
|
+
var isCompositionEndedForVideo = rootEndBehavior !== spec.EndBehavior.restart && isCompositionEnded;
|
|
860
|
+
var isVideoFrozen = (isVideoEnded || isCompositionEndedForVideo) && videoEndBehavior === spec.EndBehavior.freeze;
|
|
861
|
+
return isVideoFrozen || isCompositionFrozen;
|
|
862
|
+
};
|
|
863
|
+
/**
|
|
864
|
+
* 是否应该启动视频播放
|
|
865
|
+
*/ _proto.shouldStartVideo = function shouldStartVideo() {
|
|
866
|
+
if (this.playTriggered || !this.canPlayCurrentItem()) {
|
|
867
|
+
return false;
|
|
868
|
+
}
|
|
869
|
+
return true;
|
|
870
|
+
};
|
|
871
|
+
/**
|
|
872
|
+
* 处理延迟 seek,返回 true 表示本帧已处理 seek,应跳过后续逻辑
|
|
873
|
+
*/ _proto.processPendingSeek = function processPendingSeek() {
|
|
874
|
+
if (this.pendingSeekTime === null) {
|
|
875
|
+
return false;
|
|
876
|
+
}
|
|
877
|
+
var seekTime = this.pendingSeekTime;
|
|
878
|
+
this.pendingSeekTime = null;
|
|
879
|
+
this.performSeek(seekTime);
|
|
880
|
+
return true;
|
|
881
|
+
};
|
|
882
|
+
/**
|
|
883
|
+
* 检测合成是否发生了 restart,并重置相关状态
|
|
884
|
+
*/ _proto.detectCompositionRestart = function detectCompositionRestart() {
|
|
885
|
+
var videoTime = this.item.time;
|
|
886
|
+
if (this.lastVideoTime > 0 && videoTime < this.lastVideoTime) {
|
|
887
|
+
var _this_item_composition;
|
|
888
|
+
this.playTriggered = false;
|
|
889
|
+
this.videoDestroyed = false;
|
|
890
|
+
this.manualPause = false;
|
|
891
|
+
this.lastVideoTime = -1;
|
|
892
|
+
var rootEndBehavior = (_this_item_composition = this.item.composition) == null ? void 0 : _this_item_composition.rootItem.endBehavior;
|
|
893
|
+
var videoEndBehavior = this.item.endBehavior;
|
|
894
|
+
// 视频 restart 时,浏览器 loop 处理;合成 restart 时,前面函数已经 seek 回 0
|
|
895
|
+
if (rootEndBehavior !== spec.EndBehavior.restart && videoEndBehavior !== spec.EndBehavior.restart) {
|
|
896
|
+
this.pendingSeekTime = 0;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
this.lastVideoTime = videoTime;
|
|
900
|
+
};
|
|
901
|
+
/**
|
|
902
|
+
* 冻结视频:停止播放,保持当前帧
|
|
903
|
+
*/ _proto.freezeVideo = function freezeVideo() {
|
|
904
|
+
this.playTriggered = false;
|
|
905
|
+
if (!this.video.paused) {
|
|
906
|
+
this.pauseVideoElement();
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
/**
|
|
910
|
+
* 确保 restart 行为的视频设置了 loop 标志
|
|
911
|
+
* 手动模式下不自动设置,保持用户设置的值
|
|
912
|
+
*/ _proto.ensureLoopFlag = function ensureLoopFlag() {
|
|
913
|
+
if (this.manualLoop) {
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
if (this.item.endBehavior === spec.EndBehavior.restart && !this.video.loop) {
|
|
917
|
+
this.video.loop = true;
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
/**
|
|
921
|
+
* 处理 destroy 结束行为:视频播放到末尾后,seek 回 0 并清空纹理
|
|
922
|
+
* 确保合成 restart 时视频已在第 0 帧,不会闪最后一帧
|
|
923
|
+
*/ _proto.handleDestroyBehavior = function handleDestroyBehavior() {
|
|
924
|
+
if (this.videoDestroyed || this.videoSeeking) {
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
var isVideoEnded = this.checkVideoEnded();
|
|
928
|
+
if (isVideoEnded && this.item.endBehavior === spec.EndBehavior.destroy) {
|
|
929
|
+
this.videoDestroyed = true;
|
|
930
|
+
this.playTriggered = false;
|
|
931
|
+
this.performSeek(0, true);
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
/**
|
|
935
|
+
* 开始播放视频
|
|
936
|
+
*/ _proto.startVideo = function startVideo() {
|
|
937
|
+
if (!this.video || this.playTriggered && !this.video.paused) {
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
this.playTriggered = true;
|
|
941
|
+
this.safePlay();
|
|
942
|
+
};
|
|
943
|
+
/**
|
|
944
|
+
* 安全地调用 video.play(),串行化调用
|
|
945
|
+
*/ _proto.safePlay = function safePlay() {
|
|
946
|
+
var _this = this;
|
|
947
|
+
if (!this.video) {
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
// 已有 play() 在执行中,不重复调用,避免 AbortError
|
|
951
|
+
if (this.playPromise) {
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
var promise = this.video.play();
|
|
955
|
+
this.playPromise = promise;
|
|
956
|
+
void promise.then(function() {
|
|
957
|
+
if (_this.playPromise === promise) {
|
|
958
|
+
_this.playPromise = null;
|
|
959
|
+
}
|
|
960
|
+
_this.updatePlaybackRate();
|
|
961
|
+
}).catch(function(error) {
|
|
962
|
+
if (_this.playPromise === promise) {
|
|
963
|
+
_this.playPromise = null;
|
|
712
964
|
}
|
|
965
|
+
if (error.name === "AbortError") {
|
|
966
|
+
_this.playTriggered = false;
|
|
967
|
+
}
|
|
968
|
+
_this.engine.renderErrors.add(error);
|
|
969
|
+
});
|
|
970
|
+
};
|
|
971
|
+
/**
|
|
972
|
+
* 暂停底层视频元素
|
|
973
|
+
*/ _proto.pauseVideoElement = function pauseVideoElement() {
|
|
974
|
+
var _this = this;
|
|
975
|
+
// gotoAndStop 场景:跳过暂停,等 seek 完成后再暂停
|
|
976
|
+
if (this.isGotoAndStopSeeking) {
|
|
977
|
+
return;
|
|
713
978
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
979
|
+
if (!this.video || this.video.paused) {
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
this.video.pause();
|
|
983
|
+
if (this.playPromise) {
|
|
984
|
+
void this.playPromise.then(function() {
|
|
985
|
+
if (_this.video && !_this.video.paused) {
|
|
986
|
+
_this.video.pause();
|
|
987
|
+
}
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
/**
|
|
992
|
+
* seek 期间设置 videoSeeking=true,阻止 uploadCurrentVideoFrame 上传旧帧
|
|
993
|
+
* @param time 目标时间
|
|
994
|
+
* @param clearTexture 是否在 seek 期间清空纹理
|
|
995
|
+
* @param isGotoAndStop 是否为 gotoAndStop 场景
|
|
996
|
+
*/ _proto.performSeek = function performSeek(time, clearTexture, isGotoAndStop) {
|
|
997
|
+
var _this = this;
|
|
998
|
+
if (clearTexture === void 0) clearTexture = false;
|
|
999
|
+
if (isGotoAndStop === void 0) isGotoAndStop = false;
|
|
1000
|
+
time = this.getClampedSeekTime(time);
|
|
1001
|
+
var wasPlaying = !this.video.paused;
|
|
1002
|
+
var isNoopSeek = function() {
|
|
1003
|
+
return !clearTexture && Math.abs(_this.video.currentTime - time) <= VideoComponent.threshold;
|
|
1004
|
+
};
|
|
1005
|
+
var finishNoopSeek = function() {
|
|
1006
|
+
_this.videoSeeking = false;
|
|
1007
|
+
_this.isGotoAndStopSeeking = false;
|
|
1008
|
+
if (isGotoAndStop) {
|
|
1009
|
+
_this.video.pause();
|
|
1010
|
+
} else if (wasPlaying && !_this.manualPause) {
|
|
1011
|
+
_this.safePlay();
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
var doSeek = function() {
|
|
1015
|
+
if (isNoopSeek()) {
|
|
1016
|
+
finishNoopSeek();
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
_this.videoSeeking = true;
|
|
1020
|
+
if (clearTexture) {
|
|
1021
|
+
_this.material.setTexture("_MainTex", _this.engine.transparentTexture);
|
|
1022
|
+
}
|
|
1023
|
+
_this.video.addEventListener("seeked", function() {
|
|
1024
|
+
_this.videoSeeking = false;
|
|
1025
|
+
_this.isGotoAndStopSeeking = false;
|
|
1026
|
+
if (clearTexture) {
|
|
1027
|
+
_this.material.setTexture("_MainTex", _this.renderer.texture);
|
|
1028
|
+
}
|
|
1029
|
+
if (_this.video) {
|
|
1030
|
+
_this.renderer.texture.uploadCurrentVideoFrame();
|
|
1031
|
+
// gotoAndStop 场景:seek 完成后暂停
|
|
1032
|
+
if (isGotoAndStop) {
|
|
1033
|
+
_this.video.pause();
|
|
1034
|
+
} else if (wasPlaying && !_this.manualPause) {
|
|
1035
|
+
// 如果视频之前在播放(包括 goto 前在播放),seek 完成后恢复播放
|
|
1036
|
+
_this.safePlay();
|
|
1037
|
+
}
|
|
721
1038
|
}
|
|
722
|
-
}
|
|
723
|
-
|
|
1039
|
+
}, {
|
|
1040
|
+
once: true
|
|
1041
|
+
});
|
|
1042
|
+
_this.video.currentTime = time;
|
|
1043
|
+
};
|
|
1044
|
+
if (isGotoAndStop) {
|
|
1045
|
+
if (wasPlaying) {
|
|
1046
|
+
// 视频正在播放,直接 seek
|
|
1047
|
+
doSeek();
|
|
1048
|
+
} else if (isNoopSeek()) {
|
|
1049
|
+
finishNoopSeek();
|
|
1050
|
+
} else {
|
|
1051
|
+
// 视频暂停,先 play() 再 seek
|
|
1052
|
+
this.video.play().then(function() {
|
|
1053
|
+
doSeek();
|
|
1054
|
+
}).catch(function() {
|
|
1055
|
+
// play 失败时也尝试 seek
|
|
1056
|
+
doSeek();
|
|
1057
|
+
});
|
|
724
1058
|
}
|
|
1059
|
+
} else {
|
|
1060
|
+
if (wasPlaying) {
|
|
1061
|
+
this.video.pause();
|
|
1062
|
+
}
|
|
1063
|
+
doSeek();
|
|
1064
|
+
}
|
|
1065
|
+
};
|
|
1066
|
+
/**
|
|
1067
|
+
* 更新视频播放速率
|
|
1068
|
+
* 手动模式下保持用户设置的速率不变,自动模式下根据 engine.speed * composition.speed 计算
|
|
1069
|
+
*/ _proto.updatePlaybackRate = function updatePlaybackRate() {
|
|
1070
|
+
// 手动模式下不自动更新速率
|
|
1071
|
+
if (this.manualPlaybackRate) {
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
if (!this.video) {
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
var composition = this.item.composition;
|
|
1078
|
+
if (!composition) {
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
var playbackRate = this.engine.speed * composition.speed;
|
|
1082
|
+
if (this.video.playbackRate !== playbackRate) {
|
|
1083
|
+
this.video.playbackRate = playbackRate;
|
|
1084
|
+
}
|
|
1085
|
+
};
|
|
1086
|
+
/**
|
|
1087
|
+
* 当前 item 可播放的本地视频时间。
|
|
1088
|
+
* item.time 在 delay 前为负数,视频时间需要从 0 开始。
|
|
1089
|
+
*/ _proto.getItemSeekTime = function getItemSeekTime() {
|
|
1090
|
+
return Math.max(0, this.getItemLocalTime());
|
|
1091
|
+
};
|
|
1092
|
+
/**
|
|
1093
|
+
* 获取 item 的本地时间。优先使用 timeline 写入的 item.time,
|
|
1094
|
+
* 未写入时使用合成时间和 item delay 做兜底。
|
|
1095
|
+
*/ _proto.getItemLocalTime = function getItemLocalTime() {
|
|
1096
|
+
if (this.item.time >= 0) {
|
|
1097
|
+
return this.item.time;
|
|
1098
|
+
}
|
|
1099
|
+
var composition = this.item.composition;
|
|
1100
|
+
if (!composition) {
|
|
1101
|
+
return this.item.time;
|
|
1102
|
+
}
|
|
1103
|
+
var _this_item_definition_delay;
|
|
1104
|
+
return composition.time - ((_this_item_definition_delay = this.item.definition.delay) != null ? _this_item_definition_delay : 0);
|
|
1105
|
+
};
|
|
1106
|
+
/**
|
|
1107
|
+
* 将 seek 目标限制到视频有效时间范围内。
|
|
1108
|
+
*/ _proto.getClampedSeekTime = function getClampedSeekTime(time) {
|
|
1109
|
+
var _this_video;
|
|
1110
|
+
var seekTime = Math.max(0, time);
|
|
1111
|
+
var duration = (_this_video = this.video) == null ? void 0 : _this_video.duration;
|
|
1112
|
+
if (!duration || !isFinite(duration)) {
|
|
1113
|
+
return seekTime;
|
|
725
1114
|
}
|
|
1115
|
+
return Math.min(seekTime, duration);
|
|
1116
|
+
};
|
|
1117
|
+
/**
|
|
1118
|
+
* 组件重新启用时将视频时间对齐到 item 本地时间,但不直接触发播放。
|
|
1119
|
+
*/ _proto.syncVideoToItemTime = function syncVideoToItemTime() {
|
|
1120
|
+
if (!this.video) {
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
var seekTime = this.getItemSeekTime();
|
|
1124
|
+
var clampedSeekTime = this.getClampedSeekTime(seekTime);
|
|
1125
|
+
if (Math.abs(this.video.currentTime - clampedSeekTime) > VideoComponent.threshold) {
|
|
1126
|
+
this.pendingSeekTime = clampedSeekTime;
|
|
1127
|
+
}
|
|
1128
|
+
};
|
|
1129
|
+
/**
|
|
1130
|
+
* 当前 item 已经进入自己的时间区间,且视频允许自动播放。
|
|
1131
|
+
*/ _proto.canPlayCurrentItem = function canPlayCurrentItem() {
|
|
1132
|
+
if (!this.video || !this.isVideoActive || this.getItemLocalTime() < 0) {
|
|
1133
|
+
return false;
|
|
1134
|
+
}
|
|
1135
|
+
if (this.manualPause || this.videoDestroyed || this.videoSeeking || this.pendingSeekTime !== null) {
|
|
1136
|
+
return false;
|
|
1137
|
+
}
|
|
1138
|
+
return !this.checkVideoEnded();
|
|
726
1139
|
};
|
|
727
1140
|
/**
|
|
728
1141
|
* 获取当前视频时长
|
|
729
|
-
* @returns 视频时长
|
|
730
1142
|
*/ _proto.getDuration = function getDuration() {
|
|
731
1143
|
return this.video ? this.video.duration : 0;
|
|
732
1144
|
};
|
|
733
1145
|
/**
|
|
734
1146
|
* 获取当前视频播放时刻
|
|
735
|
-
* @returns 当前视频播放时刻
|
|
736
1147
|
*/ _proto.getCurrentTime = function getCurrentTime() {
|
|
737
1148
|
return this.video ? this.video.currentTime : 0;
|
|
738
1149
|
};
|
|
739
1150
|
/**
|
|
740
1151
|
* 设置当前视频播放时刻
|
|
741
|
-
* @param time
|
|
1152
|
+
* @param time 目标时间,会被限制在 [0, duration] 范围内
|
|
742
1153
|
*/ _proto.setCurrentTime = function setCurrentTime(time) {
|
|
743
|
-
if (this.video) {
|
|
744
|
-
|
|
1154
|
+
if (!this.video) {
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
var duration = this.video.duration;
|
|
1158
|
+
// 如果 duration 无效(如视频未加载),直接使用原值
|
|
1159
|
+
if (!duration || !isFinite(duration)) {
|
|
1160
|
+
this.pendingSeekTime = Math.max(0, time);
|
|
1161
|
+
return;
|
|
745
1162
|
}
|
|
1163
|
+
this.pendingSeekTime = Math.max(0, Math.min(time, duration));
|
|
746
1164
|
};
|
|
747
1165
|
/**
|
|
748
|
-
*
|
|
1166
|
+
* 设置视频是否循环播放,调用后会覆盖合成的结束行为,改为由用户手动控制循环。
|
|
1167
|
+
* 调用 {@link resetLoop} 可恢复为由合成结束行为自动控制。
|
|
749
1168
|
* @param loop 是否循环播放
|
|
750
1169
|
*/ _proto.setLoop = function setLoop(loop) {
|
|
1170
|
+
this.manualLoop = true;
|
|
751
1171
|
if (this.video) {
|
|
752
1172
|
this.video.loop = loop;
|
|
753
1173
|
}
|
|
754
1174
|
};
|
|
755
1175
|
/**
|
|
1176
|
+
* 重置循环播放为合成自动控制模式
|
|
1177
|
+
*/ _proto.resetLoop = function resetLoop() {
|
|
1178
|
+
this.manualLoop = false;
|
|
1179
|
+
// 将 video.loop 同步回合成应有的值,避免残留用户手动设置的状态
|
|
1180
|
+
if (this.video) {
|
|
1181
|
+
this.video.loop = this.item.endBehavior === spec.EndBehavior.restart;
|
|
1182
|
+
}
|
|
1183
|
+
};
|
|
1184
|
+
/**
|
|
756
1185
|
* 设置视频是否静音
|
|
757
1186
|
* @param muted 是否静音
|
|
758
1187
|
*/ _proto.setMuted = function setMuted(muted) {
|
|
@@ -783,115 +1212,37 @@ var VideoComponent = /*#__PURE__*/ function(MaskableGraphic) {
|
|
|
783
1212
|
this.transparent = transparent;
|
|
784
1213
|
};
|
|
785
1214
|
/**
|
|
786
|
-
*
|
|
1215
|
+
* 设置视频播放速率,调用后会覆盖合成的速率,改为由用户手动控制速率。
|
|
1216
|
+
* 调用 {@link resetPlaybackRate} 可恢复为由合成速率自动控制。
|
|
787
1217
|
* @param rate 视频播放速率
|
|
788
1218
|
*/ _proto.setPlaybackRate = function setPlaybackRate(rate) {
|
|
789
|
-
|
|
790
|
-
|
|
1219
|
+
this.manualPlaybackRate = true;
|
|
1220
|
+
if (this.video) {
|
|
1221
|
+
this.video.playbackRate = rate;
|
|
791
1222
|
}
|
|
792
|
-
this.video.playbackRate = rate;
|
|
793
1223
|
};
|
|
794
1224
|
/**
|
|
795
|
-
*
|
|
1225
|
+
* 重置播放速率为合成自动控制模式
|
|
1226
|
+
*/ _proto.resetPlaybackRate = function resetPlaybackRate() {
|
|
1227
|
+
this.manualPlaybackRate = false;
|
|
1228
|
+
};
|
|
1229
|
+
/**
|
|
1230
|
+
* 播放视频,同时取消手动暂停状态
|
|
796
1231
|
* @since 2.3.0
|
|
797
1232
|
*/ _proto.playVideo = function playVideo() {
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
return;
|
|
801
|
-
}
|
|
802
|
-
if (this.video) {
|
|
803
|
-
this.played = true;
|
|
804
|
-
this.isPlayLoading = true;
|
|
805
|
-
this.pendingPause = false;
|
|
806
|
-
this.video.play().then(function() {
|
|
807
|
-
_this.isPlayLoading = false;
|
|
808
|
-
// 如果在 play pending 期间被请求了 pause,则立即暂停并复位 played
|
|
809
|
-
if (!_this.played || _this.pendingPause) {
|
|
810
|
-
var _this_video;
|
|
811
|
-
_this.pendingPause = false;
|
|
812
|
-
_this.played = false;
|
|
813
|
-
(_this_video = _this.video) == null ? void 0 : _this_video.pause();
|
|
814
|
-
}
|
|
815
|
-
}).catch(function(error) {
|
|
816
|
-
// 复位状态
|
|
817
|
-
_this.isPlayLoading = false;
|
|
818
|
-
_this.played = false;
|
|
819
|
-
_this.pendingPause = false;
|
|
820
|
-
if (error.name !== "AbortError") {
|
|
821
|
-
_this.engine.renderErrors.add(error);
|
|
822
|
-
}
|
|
823
|
-
});
|
|
824
|
-
}
|
|
1233
|
+
this.manualPause = false;
|
|
1234
|
+
this.startVideo();
|
|
825
1235
|
};
|
|
826
1236
|
/**
|
|
827
|
-
*
|
|
1237
|
+
* 手动暂停视频
|
|
828
1238
|
* @since 2.3.0
|
|
829
1239
|
*/ _proto.pauseVideo = function pauseVideo() {
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
}
|
|
833
|
-
if (!this.video) {
|
|
834
|
-
return;
|
|
835
|
-
}
|
|
836
|
-
if (this.isPlayLoading) {
|
|
837
|
-
this.pendingPause = true;
|
|
838
|
-
return;
|
|
839
|
-
}
|
|
840
|
-
this.video.pause();
|
|
841
|
-
};
|
|
842
|
-
_proto.onDestroy = function onDestroy() {
|
|
843
|
-
var _this_item;
|
|
844
|
-
MaskableGraphic.prototype.onDestroy.call(this);
|
|
845
|
-
// 清理播放状态
|
|
846
|
-
this.played = false;
|
|
847
|
-
this.isPlayLoading = false;
|
|
848
|
-
this.pendingPause = false;
|
|
849
|
-
this.isVideoActive = false;
|
|
850
|
-
// 清理video资源
|
|
851
|
-
if (this.video) {
|
|
852
|
-
// 暂停视频
|
|
853
|
-
this.video.pause();
|
|
854
|
-
// 移除video源,帮助垃圾回收
|
|
855
|
-
this.video.removeAttribute("src");
|
|
856
|
-
this.video.load();
|
|
857
|
-
// 清理video引用
|
|
858
|
-
this.video = undefined;
|
|
859
|
-
}
|
|
860
|
-
// 清理事件监听
|
|
861
|
-
var composition = (_this_item = this.item) == null ? void 0 : _this_item.composition;
|
|
862
|
-
if (composition) {
|
|
863
|
-
if (this.handleGoto) {
|
|
864
|
-
composition.off("goto", this.handleGoto);
|
|
865
|
-
}
|
|
866
|
-
if (this.handlePause) {
|
|
867
|
-
composition.off("pause", this.handlePause);
|
|
868
|
-
}
|
|
869
|
-
if (this.handlePlay) {
|
|
870
|
-
composition.off("play", this.handlePlay);
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
// 清理处理器引用
|
|
874
|
-
this.handleGoto = undefined;
|
|
875
|
-
this.handlePause = undefined;
|
|
876
|
-
this.handlePlay = undefined;
|
|
877
|
-
};
|
|
878
|
-
_proto.onDisable = function onDisable() {
|
|
879
|
-
MaskableGraphic.prototype.onDisable.call(this);
|
|
880
|
-
this.isVideoActive = false;
|
|
881
|
-
this.pauseVideo();
|
|
882
|
-
};
|
|
883
|
-
_proto.onEnable = function onEnable() {
|
|
884
|
-
MaskableGraphic.prototype.onEnable.call(this);
|
|
885
|
-
this.isVideoActive = true;
|
|
886
|
-
this.played = false;
|
|
887
|
-
// 重播时确保视频同步到当前时间
|
|
888
|
-
if (this.video && this.item.composition) {
|
|
889
|
-
this.setCurrentTime(Math.max(0, this.item.time));
|
|
890
|
-
}
|
|
891
|
-
this.playVideo();
|
|
1240
|
+
this.manualPause = true;
|
|
1241
|
+
this.pauseVideoElement();
|
|
892
1242
|
};
|
|
893
1243
|
return VideoComponent;
|
|
894
1244
|
}(MaskableGraphic);
|
|
1245
|
+
VideoComponent.threshold = 0.01;
|
|
895
1246
|
VideoComponent = __decorate([
|
|
896
1247
|
effectsClass(spec.DataType.VideoComponent)
|
|
897
1248
|
], VideoComponent);
|
|
@@ -1235,7 +1586,7 @@ AudioComponent = __decorate([
|
|
|
1235
1586
|
|
|
1236
1587
|
/**
|
|
1237
1588
|
* 插件版本号
|
|
1238
|
-
*/ var version = "2.9.
|
|
1589
|
+
*/ var version = "2.9.1-beta.0";
|
|
1239
1590
|
registerPlugin("video", VideoLoader);
|
|
1240
1591
|
registerPlugin("audio", AudioLoader);
|
|
1241
1592
|
logger.info("Plugin multimedia version: " + version + ".");
|