Playing Video in Cocos2dx HTML5


#1

I’ve searched for an hour now without luck. A couple results on C++ version, but nothing definitive.

Any pointers on getting video rolling for HTML5? Between scenes in our app we want to show a video with the option to skip.

Thank you!


#2

Hi Randy

You can try to overlap a video element on the game canvas using HTML5 video tag , then control its visibility and play stop with css/javascript while your game scene start the transition.
The reason you can’t do it with cocos2d-html5 is that canvas doesn’t support video play natively, but we will consider to embed the video in the future.
Hope it helps

Huabin


#4

This is how we got is going: two methods in our extended scene class

     playVideo: function(videoID, cbfunc) {
		var wrapper = document.getElementById('iframe-wrapper');
		var iframe = document.getElementById('video-iframe');
		iframe.src = '//player.vimeo.com/video/' + videoID + '?title=0&byline=0&portrait=0&color=46b846&autoplay=0';
		wrapper.style.display = 'block';
		
		this.videoCallback = cbfunc || function() {};
	},
	
    finishedVideo: function() {
	  	var wrapper = document.getElementById('iframe-wrapper');
		var iframe = document.getElementById('video-iframe');
		iframe.src = '';
		wrapper.style.display = 'none';
		
		this.videoCallback();
    },

#5

Thanks for proposing the solution, it must be helpful for others who are searching for video functionality.


#6

Is there any way to play video full screen using cocos2d-js ?


#7

I hope video can be played on canvas using this.

HTML Code -

<script  src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>

<div id="theater">
<video id="video" src="http://upload.wikimedia.org/wikipedia/commons/7/79/Big_Buck_Bunny_small.ogv" controls="false"></video>
<canvas id="canvas"></canvas>
<label>
  <br />Try to play me :)</label>
<br />
</div>

Javascript -

$(function() {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var video = document.getElementById('video');

video.addEventListener('play', function() {
var $this = this; //cache
(function loop() {
  if (!$this.paused && !$this.ended) {
    ctx.drawImage($this, 0, 0);
    setTimeout(loop, 1000 / 30); // drawing at 30fps
  }
})();
}, 0);
});

I want to say if this can be achieved on canvas then it can be implemented in cocos2d-x.
In this code - ( ctx.drawImage($this, 0, 0) ) draw image area can be set. so we can get exactly what we want and can play video on any part of game.

There is running code on jsfiddle.
Demo


#8

Hey i have added a function to play video in cocos2d js and Its working for me.

you need to make some changes in GameEngine.

It will only play if you choose canvas only for your game.

I am using Cocos2dx 3.10 for my project.

How to use this.
Ex-

var videoNode = new cc.DrawNode();
var video = videoNode.playVideo('res/videos/sampleVideo.mp4', cc.p(100, 500), cc.size(640, 352));
setTimeout(function () {
    video.play();
}, 2000);
this.addChild(videoNode, 500);

Add this line in CCDrawNode.js

cc.DrawNode.TYPE_VIDEO = 4;

Add a function in CCDrawNode.js in

if (cc._renderType === cc.game.RENDER_TYPE_CANVAS)

Function to add

playVideo:function (videoSrc, position, dimension) {
    var element = new cc._DrawNodeElement(cc.DrawNode.TYPE_VIDEO);
    var video = document.createElement('video');
    video.style.display = 'none';
    video.src = videoSrc;
    element.video = video;
    element.position = position;
    element.dimension = dimension;
    this._buffer.push(element);
    return video;
},

Now change the codes in CCDrawNodeCanvasRenderCmd.js file.

(function () {

    cc.DrawNode.CanvasRenderCmd = function (renderableObject) {
        cc.Node.CanvasRenderCmd.call(this, renderableObject);
        this._needDraw = true;
        this._buffer = null;
        this._drawColor = null;
        this._blendFunc = null;
    };

    cc.DrawNode.CanvasRenderCmd.prototype = Object.create(cc.Node.CanvasRenderCmd.prototype);
    cc.DrawNode.CanvasRenderCmd.prototype.constructor = cc.DrawNode.CanvasRenderCmd;
    cc.extend(cc.DrawNode.CanvasRenderCmd.prototype, {
        rendering: function (ctx, scaleX, scaleY) {
            var wrapper = ctx || cc._renderContext, context = wrapper.getContext(), node = this._node;
            var alpha = node._displayedOpacity / 255;
            if (alpha === 0)
                return;

            wrapper.setTransform(this._worldTransform, scaleX, scaleY);

            //context.save();
            wrapper.setGlobalAlpha(alpha);
            if ((this._blendFunc && (this._blendFunc.src === cc.SRC_ALPHA) && (this._blendFunc.dst === cc.ONE)))
                wrapper.setCompositeOperation('lighter');               //todo: need refactor
            var locBuffer = this._buffer;
            for (var i = 0, len = locBuffer.length; i < len; i++) {
                var element = locBuffer[i];
                switch (element.type) {
                    case cc.DrawNode.TYPE_DOT:
                        this._drawDot(wrapper, element, scaleX, scaleY);
                        break;
                    case cc.DrawNode.TYPE_SEGMENT:
                        this._drawSegment(wrapper, element, scaleX, scaleY);
                        break;
                    case cc.DrawNode.TYPE_POLY:
                        this._drawPoly(wrapper, element, scaleX, scaleY);
                        break;
                    case cc.DrawNode.TYPE_ARC:
                        this._drawArc(wrapper, element, scaleX, scaleY);
                        break;
                    case cc.DrawNode.TYPE_VIDEO:
                        this.playVideo(wrapper, element, scaleX, scaleY);
                        break;
                }
            }
            //context.restore();            //todo It can be reserve
        },

        _drawDot: function (wrapper, element, scaleX, scaleY) {
            var locColor = element.fillColor, locPos = element.verts[0], locRadius = element.lineWidth;

            var ctx = wrapper.getContext();
            wrapper.setFillStyle("rgba(" + (0 | locColor.r) + "," + (0 | locColor.g) + "," + (0 | locColor.b) + "," + locColor.a / 255 + ")");

            ctx.beginPath();
            ctx.arc(locPos.x * scaleX, -locPos.y * scaleY, locRadius * scaleX, 0, Math.PI * 2, false);
            ctx.closePath();
            ctx.fill();
        },

        _drawSegment: function (wrapper, element, scaleX, scaleY) {
            var locColor = element.lineColor;
            var locFrom = element.verts[0], locTo = element.verts[1];
            var locLineWidth = element.lineWidth, locLineCap = element.lineCap;

            var ctx = wrapper.getContext();
            wrapper.setStrokeStyle("rgba(" + (0 | locColor.r) + "," + (0 | locColor.g) + "," + (0 | locColor.b) + "," + locColor.a / 255 + ")");

            ctx.lineWidth = locLineWidth * scaleX;
            ctx.beginPath();
            ctx.lineCap = locLineCap;
            ctx.moveTo(locFrom.x * scaleX, -locFrom.y * scaleY);
            ctx.lineTo(locTo.x * scaleX, -locTo.y * scaleY);
            ctx.stroke();
        },

        _drawPoly: function (wrapper, element, scaleX, scaleY) {
            var locVertices = element.verts, locLineCap = element.lineCap;
            if (locVertices == null)
                return;

            var locFillColor = element.fillColor, locLineWidth = element.lineWidth;
            var locLineColor = element.lineColor, locIsClosePolygon = element.isClosePolygon;
            var locIsFill = element.isFill, locIsStroke = element.isStroke;

            var ctx = wrapper.getContext();
            var firstPoint = locVertices[0];
            ctx.lineCap = locLineCap;
            if (locFillColor)
                wrapper.setFillStyle("rgba(" + (0 | locFillColor.r) + "," + (0 | locFillColor.g) + ","
                    + (0 | locFillColor.b) + "," + locFillColor.a / 255 + ")");
            if (locLineWidth)
                ctx.lineWidth = locLineWidth * scaleX;
            if (locLineColor)
                wrapper.setStrokeStyle("rgba(" + (0 | locLineColor.r) + "," + (0 | locLineColor.g) + ","
                    + (0 | locLineColor.b) + "," + locLineColor.a / 255 + ")");

            ctx.beginPath();
            ctx.moveTo(firstPoint.x * scaleX, -firstPoint.y * scaleY);
            for (var i = 1, len = locVertices.length; i < len; i++)
                ctx.lineTo(locVertices[i].x * scaleX, -locVertices[i].y * scaleY);

            if (locIsClosePolygon)
                ctx.closePath();
            if (locIsFill)
                ctx.fill();
            if (locIsStroke)
                ctx.stroke();
        },
        _drawArc: function (wrapper, element, scaleX, scaleY) {

            var locVertices = element.verts, locLineCap = element.lineCap;
            if (locVertices == null)
                return;

            var locFillColor = element.fillColor, locLineWidth = element.lineWidth;
            var locLineColor = element.lineColor, locIsClosePolygon = element.isClosePolygon;
            var locIsFill = element.isFill, locIsStroke = element.isStroke;
            var radius = element.radius;

            var ctx = wrapper.getContext();
            var firstPoint = locVertices[0];
            ctx.lineCap = locLineCap;
            if (locFillColor)
                wrapper.setFillStyle("rgba(" + (0 | locFillColor.r) + "," + (0 | locFillColor.g) + ","
                    + (0 | locFillColor.b) + "," + locFillColor.a / 255 + ")");
            if (locLineWidth)
                ctx.lineWidth = locLineWidth * scaleX;
            if (locLineColor)
                wrapper.setStrokeStyle("rgba(" + (0 | locLineColor.r) + "," + (0 | locLineColor.g) + ","
                    + (0 | locLineColor.b) + "," + locLineColor.a / 255 + ")");

            ctx.beginPath();
            ctx.moveTo(firstPoint.x * scaleX, -firstPoint.y * scaleY);

            for (var i = 1, len = locVertices.length - 1; i <= len; i++) {
                locVertices[i] && ctx.lineTo(
                    locVertices[i].x * scaleX,
                    -locVertices[i].y * scaleY
                );
                locVertices[i + 1] && ctx.quadraticCurveTo(
                    locVertices[i + 1].cpx * scaleX,
                    -locVertices[i + 1].cpy * scaleY,
                    locVertices[i + 1].x * scaleX,
                    -locVertices[i + 1].y * scaleY
                );
                ++i;
            }

            if (locIsClosePolygon)
                ctx.closePath();
            if (locIsFill)
                ctx.fill();
            if (locIsStroke)
                ctx.stroke();

        },
        playVideo: function (wrapper, element, scaleX, scaleY) {
            var video = element.video;
            if (video == null)
                return;
            var ctx = wrapper.getContext();
            var position = element.position;
            var dimension = element.dimension;
            ctx.drawImage(video, 0, 0, dimension.width, dimension.height, position.x * scaleX, -position.y * scaleY, dimension.width * scaleX, dimension.height * scaleY);
        }
    });
})();

#9

This is great… I would also like to be able to stop / pause and play… is it possible??


#10

Yes its possible!
Just use this code.

ctor: function () {
        this._super();
        var videoNode = new cc.DrawNode();
        var isVideoLoaded = false;

        this.video = videoNode.playVideo(res.video.pick, cc.p(0, 400), cc.size(844, 500), cc.size(650, 400));
        this.video.id = "demo";
        // this.video.loop = true;
        this.video.onloadeddata = function () {
            isVideoLoaded = true;
        };
        this.video.load();


        var playButtonSprite = new cc.Sprite(res.playBtn);
        playButtonSprite.setTag(112);
        playButtonSprite.setEnable = function (bool) {
            this._isEnabled = bool;
            this.setVisible(bool);//  you can init this sprite to pause sprite
        };
        playButtonSprite.setPosition(cc.p(this.width / 2, this.height / 2));
        this.addChild(playButtonSprite, 50);
        cc.eventManager.addListener(this.videolistener.clone(), playButtonSprite);
        playButtonSprite.setEnable(true);

        this.playButton = document.createElement('button');
        this.playButton.id = 'playVideo';

        var that = this;
        var promise = null;

        function startPlayback() {
            if (isVideoLoaded)
                return that.video.play();
            else return false;
        }

        function stopPlayback(){
            that.video.pause();
            promise = null;
        }

        this.video.addEventListener('ended', myHandler, false);
        function myHandler(e) {
            playButtonSprite.setEnable(true);
        }

        

        that.playButton.addEventListener('click', function () {
            if(promise) {
                stopPlayback();
                return;
            }
            promise = startPlayback();
            if (promise.then && promise.then(function () {
                    playButtonSprite.setEnable(false);
                }).catch(function (error) {
                    playButtonSprite.setEnable(true);
                })) {
                playButtonSprite.setEnable(false);
            } else playButtonSprite.setEnable(true);
        });
        this.addChild(videoNode, 5);
    },
    onTouchEndedCallBack: function (touch, event) {
        var target = event.getCurrentTarget();
        if (target.getTag() == 112) {
            this.playButton.click();
        }
    },

#11

Thanks Again.

This is great, the issue I have had is scaling. If I change the line in CCDrawNodeCanvasRenderCmd.js

To this:

ctx.drawImage(video, 0, 0, dimension.width, dimension.height, position.x, -position.y, dimension.width, dimension.height);

From this:

ctx.drawImage(video, 0, 0, dimension.width, dimension.height, position.x * scaleX, -position.y * scaleY, dimension.width * scaleX, dimension.height * scaleY);

It seems to work otherwise on smaller devices it does not keep the dimensions I provide when I switch devices in Chrome Dev tools. Was this your experience?


#12

I have been into such problem if i did not used scale factor.
Multiplying the dimensions with scale factor makes the video resizable, and this is what i wanted.


#13

I’m struggling with this. It works on desktop and on android (if I use m4v format) . I get nothing on the iPhone or iPad. I have tried the UIVideoPlayer.js as well and it wouldn’t work because (I think) the code does not support the return of a “promise” from the play method. If anyone has something that works with HTML5 across platform I would be willing to pay for the help.


#14

Why don’t you override the play function of UIVideoPlayer.js and put the code of returning “promise” into it.

You can extend the ccui.VideoPlayer class and make a custom class.
Like this.

var CustomClass = ccui.VideoPlayer.extend({
  ctor:function(){
      this._super();  // calling the parent function 
     // put any other stuff  here
  },
  play:function(){
    // play function is overridden
  }
});