import { _decorator, isValid, Node, VideoClip, UITransform, Vec3, VideoPlayer, Size, } from "cc"; import Utils from "../../Main/Common/Utils"; import li_BaseView from "../../Main/Common/li_BaseView"; import ResManager from "../../Main/Manager/ResManager"; import { DataManager, DataId } from "../../chat18x/data/DataManager"; import { EnvData } from "../../chat18x/data/EnvData"; import { logger } from "db://assets/Scripts/Main/Common/Logger"; const { ccclass, property } = _decorator; //场景背景层 - 视频模式 @ccclass("SceneBgVideoLayer") export class SceneBgVideoLayer extends li_BaseView { private _nodeTab: any = {}; private _data; static handle: SceneBgVideoLayer | null = null; private videoPlayer: VideoPlayer = null; private isPlaying: boolean = false; private currentVideoData: any = null; // 待处理的视频配置(用于在视频准备好后应用) private pendingVideoConfig: { size?: { width: number; height: number } | Size; position?: { x: number; y: number; z?: number } | Vec3; scale?: number | Vec3; } | null = null; //----重写父类接口--------------------------------------- onLoadCT() { Utils.parseNode(this.node, this._nodeTab); this.videoPlayer = this._nodeTab.videoPlayer.getComponent(VideoPlayer); if (!this.videoPlayer) { logger.error("SceneBgVideoLayer: video节点未找到或没有VideoPlayer组件"); } this.initVideoEvents(); SceneBgVideoLayer.handle = this; } openUIDataCT(data: any): void { this._data = data; } getVideoClone() { return this.videoPlayer; } /**初始化视频事件监听 */ private initVideoEvents() { if (!this.videoPlayer) return; // 移除之前的事件监听器,避免重复绑定 this.videoPlayer.node.off( VideoPlayer.EventType.READY_TO_PLAY, this.onVideoReadyToPlay, this ); //this.videoPlayer.node.off(VideoPlayer.EventType.VIDEO_PLAYER_EVENT, this.onVideoEvent, this); // 添加新的事件监听 this.videoPlayer.node.on( VideoPlayer.EventType.READY_TO_PLAY, this.onVideoReadyToPlay, this ); // this.videoPlayer.node.on(VideoPlayer.EventType.VIDEO_PLAYER_EVENT, this.onVideoEvent, this); } //================== 公共 API 方法 ================== /** * 播放视频 - 统一的视频播放接口 * @param path 视频路径 * @param options 可选配置项 * @param options.loop 是否循环播放,默认true * @param options.size 视频尺寸 {width, height} * @param options.position 视频位置 {x, y, z} * @param options.scale 视频缩放值 * @param options.autoFill 是否自动填充到指定尺寸 */ playVideo( path: string, options?: { loop?: boolean; size?: { width: number; height: number } | Size; position?: { x: number; y: number; z?: number } | Vec3; scale?: number | Vec3; } ) { if (!this.videoPlayer) { logger.error("SceneBgVideoLayer: VideoPlayer未初始化"); return; } // 默认参数 const config = { loop: options?.loop ?? true, size: options?.size, position: options?.position, }; // 先停止当前视频并清理状态 if (this.isPlaying) { this.stop(); } // 存储待处理的配置,在视频准备好后应用 this.pendingVideoConfig = { size: config.size, position: config.position, scale: options?.scale, }; const envData = DataManager.I.getDataById(DataId.Env); let newPath = envData.cdn + "/" + "Girls/" + path + ".mp4"; // 加载远程资源 ResManager.I.loadRemoteVideo( this.videoPlayer, newPath, (res: VideoClip) => { this.videoPlayer.loop = config.loop; // 设置锚点为中心,确保位置对齐 const uiTransform = this.videoPlayer.node.getComponent(UITransform); if (uiTransform) { uiTransform.setAnchorPoint(0.5, 0.5); logger.log("SceneBgVideoLayer: 设置视频锚点为 (0.5, 0.5)"); } // 立即开始播放,尺寸和位置将在 READY_TO_PLAY 事件中设置 this.videoPlayer.play(); this.isPlaying = true; logger.log(`SceneBgVideoLayer: 开始加载视频 ${path}, 配置:`, config); } ); } /** * 暂停视频 */ pause() { if (this.videoPlayer && this.isPlaying) { this.videoPlayer.pause(); logger.log("SceneBgVideoLayer: 暂停视频"); } } /** * 继续播放视频 */ resume() { if (this.videoPlayer && !this.videoPlayer.isPlaying) { this.videoPlayer.play(); logger.log("SceneBgVideoLayer: 继续播放视频"); } } /** * 停止视频 */ stop() { if (this.videoPlayer) { this.videoPlayer.stop(); this.clearVideoState(); logger.log("SceneBgVideoLayer: 停止视频"); } } /** * 设置视频缩放 * @param scale 缩放值(数字或Vec3) */ setVideoScale(scale: number | Vec3) { if (this.videoPlayer) { if (typeof scale === "number") { this.videoPlayer.node.setScale(new Vec3(scale, scale, 1)); logger.log(`SceneBgVideoLayer: 设置视频缩放 ${scale}`); } else { this.videoPlayer.node.setScale(scale); logger.log( `SceneBgVideoLayer: 设置视频缩放 (${scale.x}, ${scale.y}, ${scale.z})` ); } } } /** * 调整视频以填满指定区域 * @param targetSize 目标尺寸 * @param position 可选的位置 */ adjustToFillArea(targetSize: Size, position?: Vec3) { if (!this.videoPlayer) return; // 如果视频正在播放,直接应用配置 if (this.isPlaying && this.videoPlayer.isPlaying) { this.applyVideoConfiguration({ size: targetSize, position: position, }); } else { // 如果视频还未准备好,存储配置等待应用 this.pendingVideoConfig = { size: targetSize, position: position, }; logger.log("SceneBgVideoLayer: 视频未准备好,配置已存储等待应用"); } } /** * 清除视频状态和待处理配置 */ private clearVideoState() { this.isPlaying = false; this.pendingVideoConfig = null; this.currentVideoData = null; } //================== 内部辅助方法 ================== /** * 应用视频配置(位置、尺寸、缩放) * @param config 视频配置对象 */ private applyVideoConfiguration(config: { size?: { width: number; height: number } | Size; position?: { x: number; y: number; z?: number } | Vec3; scale?: number | Vec3; }) { if (!this.videoPlayer) return; const uiTransform = this.videoPlayer.node.getComponent(UITransform); if (!uiTransform) return; // 应用位置设置 if (config.position) { const pos = config.position; let targetWorldPos: Vec3; if (pos instanceof Vec3) { targetWorldPos = pos; } else { targetWorldPos = new Vec3(pos.x, pos.y, pos.z || 0); } this.videoPlayer.node.setPosition(targetWorldPos); logger.log( `SceneBgVideoLayer: 设置视频位置 (${targetWorldPos.x}, ${targetWorldPos.y}, ${targetWorldPos.z})` ); } // 应用尺寸和缩放设置 if (config.size) { // 获取视频的原始尺寸(此时应该是准确的) const videoSize = uiTransform.contentSize; if (videoSize.width > 0 && videoSize.height > 0) { const targetWidth = config.size instanceof Size ? config.size.width : config.size.width; const targetHeight = config.size instanceof Size ? config.size.height : config.size.height; // 计算缩放比例(填满整个区域) const scaleX = targetWidth / videoSize.width; const scaleY = targetHeight / videoSize.height; const scale = Math.max(scaleX, scaleY); this.videoPlayer.node.setScale(new Vec3(scale, scale, 1)); logger.log( `SceneBgVideoLayer: 设置视频缩放 ${scale} (原始尺寸: ${videoSize.width}x${videoSize.height}, 目标尺寸: ${targetWidth}x${targetHeight})` ); } else { logger.warn("SceneBgVideoLayer: 无法获取视频尺寸,跳过缩放设置"); } } else if (config.scale) { // 如果显式传入了 scale 参数,直接应用 if (typeof config.scale === "number") { this.videoPlayer.node.setScale(new Vec3(config.scale, config.scale, 1)); logger.log(`SceneBgVideoLayer: 设置显式缩放 ${config.scale}`); } else { this.videoPlayer.node.setScale(config.scale); logger.log( `SceneBgVideoLayer: 设置显式缩放 (${config.scale.x}, ${config.scale.y}, ${config.scale.z})` ); } } else { // 没有指定尺寸或缩放,保持原始缩放 this.videoPlayer.node.setScale(Vec3.ONE); logger.log("SceneBgVideoLayer: 保持默认缩放 (1, 1, 1)"); } } //================== 事件回调 ================== private onVideoReadyToPlay = () => { logger.log("SceneBgVideoLayer: 视频准备完成"); // 应用待处理的视频配置 if (this.pendingVideoConfig) { this.applyVideoConfiguration(this.pendingVideoConfig); // 清空待处理配置 this.pendingVideoConfig = null; } this.onVideoReady(); }; private onVideoEvent = (event: any, eventType: string) => { switch (eventType) { case "completed": logger.log("SceneBgVideoLayer: 视频播放完成"); this.clearVideoState(); this.onVideoEnd(); break; case "error": logger.error("SceneBgVideoLayer: 视频播放错误"); this.clearVideoState(); this.onVideoError(); break; } }; /** * 视频准备完成回调(可重写) */ protected onVideoReady() { // 由子类重写或外部监听 } /** * 视频播放完成回调(可重写) */ protected onVideoEnd() { // 由子类重写或外部监听 } /** * 视频播放错误回调(可重写) */ protected onVideoError() { // 由子类重写或外部监听 } //================== 其他方法 ================== /** * 获取当前播放状态 */ getIsPlaying(): boolean { return this.isPlaying; } /** * 获取VideoPlayer组件引用 */ getVideoPlayer(): VideoPlayer | null { return this.videoPlayer; } /** * 销毁时清理资源 */ onDestroy() { if (this.videoPlayer) { this.videoPlayer.stop(); this.videoPlayer.node.off( VideoPlayer.EventType.READY_TO_PLAY, this.onVideoReadyToPlay, this ); //this.videoPlayer.node.off(VideoPlayer.EventType.VIDEO_PLAYER_EVENT, this.onVideoEvent, this); } this.clearVideoState(); SceneBgVideoLayer.handle = null; } }