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

TypeScript 复制代码
/**
 * 对外输出接口
 */
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;
    }
}

使用样例:

TypeScript 复制代码
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]);
    }
}
相关推荐
小码哥kylin9 天前
spine动画监听动作播放完成重复执行
cocoscreator·spine
爱你的魔1 年前
cocosCreator Tab内容布局组件(支持编辑器环节使用)
cocoscreator
w风雨无阻w1 年前
CocosCreator3.8 IOS 构建插屏无法去除的解决方案
ios·cocoscreator·cocoscreator3.8·插屏
w风雨无阻w1 年前
CocosCreator 3.8 IOS 热更新失败问题解决方案
android·ios·cocoscreator·热更新·cocoscreator3.8
椰子糖莫莫1 年前
游戏引擎详解——图片
游戏引擎·cocoscreator
Fuly10241 年前
Cocos Creator2D游戏开发(14)---CocosCreator常用组件详解
cocoscreator
天马流星27191 年前
cocosCreator3.X android 真机wifi 调试远程死活无效的问题
android·android studio·cocoscreator
Fuly10241 年前
Cocos Creator2D游戏开发(5)-飞机大战(3)-手指操作玩家飞机移动
cocoscreator
Fuly10241 年前
Cocos Creator2D游戏开发(4)-飞机大战(2)-编辑器界面
cocoscreator