cocosCreator视频web模式播放踩坑解决

/**
 * 对外输出接口
 */
export interface VideoPlayerManageInterface {
    //初始化视频播放器
    init(
        list: VideoPlayerManageInitListType[],
        options?: VideoPlayerManageInitOptionsType
    ): Promise<void>;

    //播放视频
    play(url: string, currentTime?: number): Promise<void>;

    //销毁视频
    destroyUrl(url: string, currentTag: string): void;

    //销毁标签的所有视频
    destroyTag(targetTag: string): void;
}

export type VideoPlayerManageInitListType = {
    //标签,分组
    tag: string;
    //视频地址
    url: string;
    //视频宽度
    width: number;
    //视频高度
    height: number;
    //是否循环播放
    isLoop: boolean;
    //视频播放结束回调(非循环下生效)
    endCallback?: () => void;
};

export type VideoPlayerManageInitOptionsType = {
    //视频封面地址
    poster?: string;
    //播放按钮地址
    playerStartButtonUrl?: string;
    //是否已经点击过播放按钮
    hasClicked?: boolean;
    //是否是苹果设备检测函数
    checkIsAppleFun?: () => boolean;
};

/**
 * 鉴于cocos对于视频播放的支持不够完善,在自带的VideoPlayer组件上进行了封装
 * 使用VideoPlayerManage进行视频的提前装载,播放,销毁等操作
 * 解决视频切换时的黑屏问题、视频默认样式修改、ISO自动播放、播放时未就绪报错的问题
 * web环境使用
 */

cc.macro.ENABLE_TRANSPARENT_CANVAS = true;

export default class VideoPlayerManage implements VideoPlayerManageInterface {
    private static V: VideoPlayerManageInterface = null;

    public static get instance(): VideoPlayerManageInterface {
        if (!this.V) {
            this.V = new VideoPlayerManage();
        }
        return this.V;
    }

    private readonly CLASS_NAME = "cocosVideo";

    private poster = ""; //这里是一个视频封面的图片地址

    private playerStartButtonUrl = ""; //这里是一个播放按钮的图片地址

    private hasClicked = false;

    constructor() {
        cc.Canvas.instance.node.getComponentInChildren(
            cc.Camera
        ).backgroundColor = new cc.Color(0, 0, 0, 0);
    }

    private map: Map<
        string,
        {
            url: string;
            videoPlayer: cc.VideoPlayer;
            tag: string[];
        }
    > = new Map();

    async init(
        list: VideoPlayerManageInitListType[],
        options?: VideoPlayerManageInitOptionsType
    ): Promise<void> {
        Object.keys(options).forEach((key) => {
            this[key] = options[key];
        });

        await Promise.all(
            list.map((listItem) => this.initVideoPlayerCore(listItem))
        );
        await this.initVideos();
    }

    async play(url: string, currentTime = 0) {
        const videoPlayer = this.map.get(url).videoPlayer;

        this.map.forEach((value) => {
            value.videoPlayer.node.active = url === value.url;
        });

        videoPlayer.currentTime = currentTime;
        if (videoPlayer.isPlaying()) {
            videoPlayer.pause();
        }

        videoPlayer.node.off("ready-to-play");
        videoPlayer.node.on(
            "ready-to-play",
            () => {
                if (!videoPlayer.isPlaying()) {
                    videoPlayer.play();
                }
            },
            this
        );

        await this.waitPlayerClick();

        if (!videoPlayer.isPlaying()) {
            videoPlayer.play();
        } else {
            videoPlayer.resume();
        }
    }

    destroyUrl(url: string, currentTag: string) {
        const item = this.map.get(url);
        if (!item) return;
        if (item.tag.length > 1) {
            item.tag = item.tag.filter((tagItem) => {
                return tagItem !== currentTag;
            });
            return;
        }
        this.delOneVideo(item);
    }

    destroyTag(targetTag: string) {
        this.map.forEach((item) => {
            if (item.tag.includes(targetTag)) {
                item.tag = item.tag.filter((tagItem) => {
                    return tagItem !== targetTag;
                });
                if (item.tag.length === 0) {
                    this.delOneVideo(item);
                }
            }
        });
    }

    private delOneVideo(item) {
        item.videoPlayer.node.destroy();
        const videoDom = this.getVideoDom(item.url);
        if (videoDom) {
            videoDom.parentNode.removeChild(videoDom);
        }
        this.map.delete(item.url);
    }

    private async initVideos() {
        await this.delayOneFrame();
        const list = document.getElementsByClassName(this.CLASS_NAME);
        for (let i = 0; i < list.length; i++) {
            const video: Element = list[i];
            video["poster"] = this.poster;
            video["autoplay"] = true;
        }
    }

    private async initVideoPlayerCore(listItem) {
        const videoMapItem = this.map.get(listItem.url);
        if (videoMapItem) {
            !videoMapItem.tag.includes(listItem.tag) &&
                videoMapItem.tag.push(listItem.tag);
            return;
        }
        const videoPlayer = await this.createVideoPlayerForUrl(listItem);
        this.map.set(listItem.url, {
            url: listItem.url,
            videoPlayer,
            tag: [listItem.tag],
        });
    }

    private async createVideoPlayerForUrl(listItem): Promise<cc.VideoPlayer> {
        const videoNode: cc.Node = new cc.Node();
        videoNode.active = false;
        const videoPlayer = videoNode.addComponent(cc.VideoPlayer);
        videoPlayer.mute = true;
        videoPlayer.resourceType = cc.VideoPlayer.ResourceType.LOCAL;
        videoNode.width = listItem.width;
        videoNode.height = listItem.height;
        videoPlayer.stayOnBottom = true;
        cc.Canvas.instance.node.addChild(videoNode);
        const asset = await this.loadVideo(listItem.url);
        videoPlayer.clip = asset as unknown as string;
        this.setLoopAndEndCallBack(
            videoPlayer,
            listItem.isLoop,
            listItem.endCallback
        );
        return videoPlayer;
    }

    private loadVideo(url: string) {
        return new Promise((resolve, reject) => {
            cc.assetManager.loadRemote(
                url,
                { ext: ".mp4" },
                (err, asset: cc.Asset) => {
                    if (err) {
                        reject(err);
                    } else {
                        resolve(asset);
                    }
                }
            );
        });
    }

    private loadPng(url: string) {
        return new Promise((resolve, reject) => {
            cc.assetManager.loadRemote(
                url,
                { ext: ".png" },
                (err, asset: cc.Asset) => {
                    if (err) {
                        reject(err);
                    } else {
                        resolve(asset);
                    }
                }
            );
        });
    }

    private delayOneFrame(): Promise<void> {
        return new Promise((resole) => {
            cc.Canvas.instance.scheduleOnce(() => {
                resole();
            });
        });
    }

    private setLoopAndEndCallBack(
        videoPlayer: cc.VideoPlayer,
        isLoop: boolean,
        endCallback: () => void
    ) {
        videoPlayer.node.off("completed");
        videoPlayer.node.on(
            "completed",
            () => {
                if (isLoop) {
                    videoPlayer.currentTime = 0;
                    videoPlayer.play();
                } else {
                    endCallback && endCallback();
                }
            },
            this
        );
    }

    private async waitPlayerClick(): Promise<void> {
        return new Promise((resolve) => {
            if (this.hasClicked || !this.checkIsApple()) {
                resolve();
                return;
            }
            const node = new cc.Node();
            node.addComponent(cc.BlockInputEvents);
            const sprite = node.addComponent(cc.Sprite);
            this.loadPng(this.playerStartButtonUrl).then((asset) => {
                sprite.spriteFrame = new cc.SpriteFrame(
                    asset as unknown as cc.Texture2D
                );
                node.setPosition(cc.v2(0, 0));
                cc.Canvas.instance.node.addChild(node);
                node.once(cc.Node.EventType.TOUCH_END, () => {
                    node.destroy();
                    this.hasClicked = true;
                    resolve();
                });
            });
        });
    }

    private checkIsApple() {
        return /iphone|ipad|ios|mac/gi.test(navigator.userAgent.toLowerCase());
    }

    private getVideoDom(url: string) {
        const list = document.getElementsByClassName(this.CLASS_NAME);
        for (let i = 0; i < list.length; i++) {
            const video = list[i];
            if (url == video["src"]) {
                return video;
            }
        }
        return null;
    }
}

使用样例:

import VideoPlayerManage from "./VideoPlayerManage";

const { ccclass } = cc._decorator;

@ccclass
export default class Index extends cc.Component {
    private list = [
        "http://localhost:3000/light1.mp4",
        "http://localhost:3000/light2.mp4",
    ];

    private index = 0;

    protected async onLoad(): Promise<void> {
        await VideoPlayerManage.instance.init(
            this.list.map((url) => {
                return {
                    tag: url,
                    url,
                    width: this.node.width,
                    height: this.node.height,
                    isLoop: true,
                    endCallback: () => {
                        console.log("end");
                    },
                };
            }),
            {
                poster: "",
                playerStartButtonUrl: "http://localhost:3000/head1.png",
            }
        );

        this.playByIndex();

        this.node.on(cc.Node.EventType.TOUCH_END, () => {
            this.playByIndex();
        });
    }

    playByIndex() {
        this.index++;
        if (this.index >= this.list.length) {
            this.index = 0;
        }
        VideoPlayerManage.instance.play(this.list[this.index]);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/917586.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

正则表达式完全指南,总结全面通俗易懂

目录 元字符 连接符 限定符 定位符 修饰符&#xff08;标记&#xff09; 运算符优先级 普通字符集及其替换 零宽断言 正向先行断言 负向先行断言 正向后发断言 负向后发断言 正则表达式在线测试: 正则在线测试工具 元字符 字符描述\d 匹配一个数字字符。等价于 …

知识库管理系统:企业数字化转型的加速器

在数字化转型的大潮中&#xff0c;知识库管理系统&#xff08;KBMS&#xff09;已成为企业提升效率和创新能力的关键工具。本文将探讨知识库管理系统的定义、企业建立知识库的必要性&#xff0c;以及如何快速搭建企业知识库。 知识库管理系统是什么&#xff1f; 知识库管理系统…

【珠海科技学院主办,暨南大学协办 | IEEE出版 | EI检索稳定 】2024年健康大数据与智能医疗国际会议(ICHIH 2024)

#IEEE出版|EI稳定检索#主讲嘉宾阵容强大&#xff01;多位外籍专家出席报告 2024健康大数据与智能医疗国际会议&#xff08;ICHIH 2024&#xff09;2024 International Conference on Health Big Data and Intelligent Healthcare 会议简介 2024健康大数据与智能医疗国际会议…

【软件测试】一个简单的自动化Java程序编写

文章目录 自动化自动化概念回归测试常见面试题 自动化测试金字塔 Web 自动化测试驱动 Selenium一个简单的自动化示例安装 selenium 库使⽤selenium编写代码 自动化 自动化概念 自动的代替人的行为完成操作。自动化在生活中处处可见 生活中的自动化可以减少人力的消耗&#x…

️️一篇快速上手 AJAX 异步前后端交互

AJAX 1. AJAX1.1 AJAX 简介1.2 AJAX 优缺点1.3 AJAX 前后端准备1.4 AJAX 请求基本操作1.5 AJAX 发送 POST 请求1.6 设置请求头1.7 响应 JSON 数据1.8 AJAX 请求超时与网络异常处理1.9 取消请求1.10 Fetch 发送 Ajax 请求 2. jQuery-Ajax2.1 jQuery 发送 Ajax 请求&#xff08;G…

【星海随笔】ZooKeeper-Mesos

开源的由 Twitter 与 伯克利分校的 Mesos 项目组共同研发设计。 两极调度架构 支持高可用集群&#xff0c;通过ZooKeeper进行选举。 Mesos master 管理着所有的 Mesos slave 守护进程 每个slave运行具体的任务或者服务。 Franework 包括的调度器和执行机两部分 执行器运行在Me…

计算机网络中的域名系统(DNS)及其优化技术

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 计算机网络中的域名系统&#xff08;DNS&#xff09;及其优化技术 计算机网络中的域名系统&#xff08;DNS&#xff09;及其优化…

Vulnhub靶场案例渗透[10]- Momentum2

文章目录 一、靶场搭建1. 靶场描述2. 下载靶机环境3. 靶场搭建 二、渗透靶场1. 确定靶机IP2. 探测靶场开放端口及对应服务3. 扫描网络目录结构4. 代码审计5. 反弹shell6. 提权 一、靶场搭建 1. 靶场描述 - Difficulty : medium - Keywords : curl, bash, code reviewThis wor…

如何在 Ubuntu 上安装 Emby 媒体服务器

Emby 是一个开源的媒体服务器解决方案&#xff0c;它能让你整理、流媒体播放和分享你的个人媒体收藏&#xff0c;包括电影、音乐、电视节目和照片。Emby 帮你集中多媒体内容&#xff0c;让你无论在家还是在外都能轻松访问。它还支持转码&#xff0c;让你能够播放各种格式的内容…

java作业项目以及azkaban的操作

参考内容&#xff1a; azkaban简介及azkaban部署、原理和使用介绍 1.在azkaban创建project 2.上传flow文件和project文件的压缩包 flow文件内容: nodes:- name: Testtype: commandconfig:command: java -jar /data/job/mtm-job-0.0.1-SNAPSHOT.jar --spring.profiles.activ…

2024-11-15 Element-ui的tab切换中table自适应宽度无法立即100%的问题

前言 今天在写一个统计图表的时候&#xff0c;将所有的table表格和echarts图表放到一个页面中&#xff0c;这样会在纵向上出现滚动条&#xff0c;上下滑动对用户体验不好&#xff0c;于是改成tab切换的形式 遇到的问题 正如标题所述&#xff0c;elementui在tab中使用table时&…

HarmonyOS 开发环境搭建

HarmonyOS&#xff08;鸿蒙操作系统&#xff09;作为一种面向全场景多设备的智能操作系统&#xff0c;正逐渐在市场上崭露头角。为了进入HarmonyOS生态&#xff0c;开发者需要搭建一个高效的开发环境。本文将详细介绍如何搭建HarmonyOS开发环境&#xff0c;特别是如何安装和配置…

Vue全栈开发旅游网项目(10)-用户管理后端接口开发

1.异步用户登录\登出接口开发 1.设计公共响应数据类型 文件地址&#xff1a;utils/response404.py from django.http import JsonResponseclass BadRequestJsonResponse(JsonResponse):status_code 400def __init__(self, err_list, *args, **kwargs):data {"error_c…

快速了解Memcached

快速了解Memcached 官方定义 Memcached 是一个高性能的分布式内存对象缓存系统&#xff0c;用于减轻数据库负载&#xff0c;通过在内存中缓存数据和对象来提高动态 web 应用程序的响应速度。 主要特点 简单高效&#xff1a;其设计理念就是简单&#xff0c;易于部署和使用。它是…

【Android、IOS、Flutter、鸿蒙、ReactNative 】启动页

Android 设置启动页 自定义 splash.xml 通过themes.xml配置启动页背景图 IOS 设置启动页 LaunchScreen.storyboard 设置为启动页 storyboard页面绘制 Assets.xcassets 目录下导入图片 AppLogo Flutter 设置启动页 Flutter Android 设置启动页 自定义 launch_background.xm…

【插件】多断言 插件pytest-assume

背景 assert 断言一旦失败&#xff0c;后续的断言不能被执行 有个插件&#xff0c;pytest-assume的插件&#xff0c;可以提供多断言的方式 安装 pip3 install pytest-assume用法 pytest.assume(表达式,f’提示message’) pytest.assume(表达式,f‘提示message’) pytest.ass…

DDRPHY数字IC后端设计实现系列专题之数字后端floorplanpowerplan设计

3.2.3 特殊单元的布局 布图阶段除了布置 I/O 单元和宏单元&#xff0c;在 28nm 制程工艺时&#xff0c;还需要处理两种特 殊的物理单元&#xff0c;Endcap 和 Tapcell。 DDRPHY数字IC后端设计实现系列专题之后端设计导入&#xff0c;IO Ring设计 &#xff08;1&#xff09;拐…

Java 全栈知识体系

包含: Java 基础, Java 部分源码, JVM, Spring, Spring Boot, Spring Cloud, 数据库原理, MySQL, ElasticSearch, MongoDB, Docker, k8s, CI&CD, Linux, DevOps, 分布式, 中间件, 开发工具, Git, IDE, 源码阅读&#xff0c;读书笔记, 开源项目...

【Docker系列】如何在 Docker 环境中部署和运行 One API

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

录的视频怎么消除杂音?从录制到后期的杂音消除攻略

在录制视频时&#xff0c;杂音往往是一个令人头疼的问题。无论是环境噪音、设备噪音还是电磁干扰&#xff0c;杂音的存在都会极大地影响视频的听觉体验。录的视频怎么消除杂音&#xff1f;通过一些前期准备和后期处理技巧&#xff0c;我们可以有效地消除这些杂音&#xff0c;提…