Video texture on a ui element

Hi,

Is it possible to place a video texture on a ui entity? I’ve seen the tutorial to place it onto a 3d object.

Also is it possible to control the compression that play canvas seem to add when videos are uploaded?

thanks!

Hi @davidj2022,

Yes, it’s possible, check this post:

I don’t think PlayCanvas adds any compression on the video file. Maybe you are talking about how WebGL textures are filtered when rendered in 3D? Can you share some example on what you see as comrpession?

1 Like

thank you… yeah adding world as well as ui got it working on the 2d screen. It still looks like like quite a bad quality video though in terms of anti aliasing… will try and show an example…

are video textures quite computational expensive btw?

thanks for your help :slight_smile:

I think you can have only a handful, on mobile even less. It depends on the size/resolution of the video. Both the video playback can be expensive (browser side) and upload the texture to the GPU per frame.

1 Like

I have over 20MB of videos on my experience, 10 videos total. They all play fine. PlayCanvas is really optimized. It’s harder to use than Unity and other easier tools, but damn the devs deserve all the praise for performance. In Unity or Studio it’d be a slideshow with even 2 videos.

I updated the videoTexture script, I’ll share it here. It just exposes more parameters and shows the first frame of the vid.

Object.defineProperty(HTMLVideoElement.prototype, 'playing', {

    get: function(){

        return !!(this.currentTime > 0 && !this.paused && !this.ended && this.readyState > 2);

    }

});

var VideoTexture = pc.createScript('videoTexture');

VideoTexture.attributes.add('video', {

    title: 'Video',

    description: 'MP4 video asset to play back on this video texture.',

    type: 'asset'

});

VideoTexture.attributes.add('playEvent', {

    title: 'Play Event',

    description: 'Event that is fired as soon as the video texture is ready to play.',

    type: 'string',

    default: ''

});

VideoTexture.attributes.add('autoPlay', {

    title: 'Auto Play',

    description: 'If true the video autoplays, otherwise play needs to be called manually',

    type: 'boolean',

    default: true

});

VideoTexture.attributes.add('loop', {

    title: 'Loop',

    description: 'If true the video loops, otherwise it doesn\'t',

    type: 'boolean',

    default: true

});

VideoTexture.attributes.add('muted', {

    title: 'Muted',

    description: 'If true the video starts muted.',

    type: 'boolean',

    default: true

});

// initialize code called once per entity

VideoTexture.prototype.initialize = function() {

    var app = this.app;

   

    // Create HTML Video Element to play the video

    var video = this.videoEl = document.createElement('video');

    video.loop = this.loop;

    // muted attribute is required for videos to autoplay

    video.muted = this.muted;

    // critical for iOS or the video won't initially play, and will go fullscreen when playing

    video.playsInline = true;

   

    // needed because the video is being hosted on a different server url

    video.crossOrigin = "anonymous";

   

    // autoplay the video

    video.autoplay = this.autoPlay;

   

    //https://stackoverflow.com/questions/7323053/dynamically-using-the-first-frame-as-poster-in-html5-video

    //preloaded metadata trick described above doesn't work for me

    //video.preload="metadata";

    // iOS video texture playback requires that you add the video to the DOMParser

    // with at least 1x1 as the video's dimensions

    var style = video.style;

    style.width = '1px';

    style.height = '1px';

    style.position = 'absolute';

    style.opacity = '0';

    style.zIndex = '-1000';

    style.pointerEvents = 'none';

    document.body.appendChild(video);

    // Create a texture to hold the video frame data            

    this.videoTexture = new pc.Texture(app.graphicsDevice, {

        format: pc.PIXELFORMAT_R8_G8_B8,

        minFilter: pc.FILTER_LINEAR_MIPMAP_LINEAR,

        magFilter: pc.FILTER_LINEAR,

        addressU: pc.ADDRESS_CLAMP_TO_EDGE,

        addressV: pc.ADDRESS_CLAMP_TO_EDGE,

        mipmaps: true

    });

    this.videoTexture.setSource(video);

    video.addEventListener('loadeddata', function (e) {

        if (this.autoplay) {

            this.videoTexture.upload();

            app.fire(this.playEvent, this.videoTexture);

        }

        else {

            setTimeout(() => {

                this.videoTexture.upload();

                app.fire(this.playEvent, this.videoTexture);

            }, 1000);//this timeout shouldn't be necessary, but it's the only way I know to guarantee and display the first frame of the video, otherwise you might get a black frame.

        }

    }.bind(this), { once: true});

    // set video source

    if (this.video) video.src = this.video.getFileUrl();

   

    document.body.appendChild(video);

    video.load();

};

// update code called every frame

VideoTexture.prototype.update = function(dt) {

    // Transfer the latest video frame to the video texture

    this.videoEl.playing && this.videoTexture.upload();//without checking if can play first and without the timeout on loadeddata, some videos can lag.

};

VideoTexture.prototype.play = function() {

    this.videoEl.play();

};

VideoTexture.prototype.pause = function() {

    this.videoEl.pause();

    this.videoEl.currentTime = 0;

};
1 Like