Vue VideoJS in UIBuilder

I agree! Being able to start Vue with Node-Red data plasticity helped by uibuilder is really great, but it's a pain to understand at first, too many details and a hard time to tackle problems. Steep learning curve!
Hopefully a great community goes a long way :grinning_face_with_smiling_eyes:

Actually I want to call different videos so I guess the container is the best way in my case.
Removing the first player and moving options to inde.js

    data: {
        player: null,
        videoOptions: {
			class:"video-js",
            controls:true,
            preload:"auto",
            width:640,
            height:264,
			sources: [

throws new errors:

[Vue warn]: Error in mounted hook: "TypeError: The element or ID supplied is not valid. (videojs)"

(found in <Root>) vue.js:634:17
    VueJS 11
    <anonymous> http://192.168.1.236:1880/videojs/index.js:3
TypeError: The element or ID supplied is not valid. (videojs)

Nope, I don't think so, you are over-complecating things. A single player will show as many videos as you like (1 at a time), simply send a new one from Node-RED. Or, better still, send a list of videos with metadata from Node-RED to your front-end, use that list to build a dropdown and use the change event on the dropdown to load the appropriate video and start playing.

Then I still miss something in the setup :slight_smile:
How would I do that?
What about the error?

The error gives a clear indication where to look.
You mentioned that you removed the first video player component from Julian's example.
But in index.js in mounted() the code tries to initialize a player in an element with id 'my-video' and none is found.

 app.player = videojs('my-video')
        console.log(app.player)

I didnt study the code in depth but try use an id="my-video" on the second player that you have in your modified code.

1 Like

Thanks @UnborN
In index.html, I'd like to keep <video-player :options="videoOptions"/> to understand the uibuilder-Vue mechanic.
So here's my index.js:

'use strict'

new Vue({
    el: '#app',
    
    components: {
        'video-player': httpVueLoader('video-player.vue'),
    }, // --- End of components --- //
    
    data: {
        player: null,
        videoOptions: {
			id:"video-player",
			class:"video-js",
            controls:true,
            preload:"auto",
            width:640,
            height:264,
			sources: [
				{
					src:
						'//vjs.zencdn.net/v/oceans.mp4',
					  type: 'video/mp4'
				}
			]
		},
		videoUrl: '',
		
        startMsg    : 'Vue has started, waiting for messages',
        feVersion   : '',
        counterBtn  : 0,
        inputText   : null,
        inputChkBox : false,
        socketConnectedState : false,
        serverTimeOffset     : '[unknown]',
        imgProps             : { width: 75, height: 75 },

        msgRecvd    : '[Nothing]',
        msgsReceived: 0,
        msgCtrl     : '[Nothing]',
        msgsControl : 0,

        msgSent     : '[Nothing]',
        msgsSent    : 0,
        msgCtrlSent : '[Nothing]',
        msgsCtrlSent: 0,

        isLoggedOn  : false,
        userId      : null,
        userPw      : null,
        inputId     : '',
    }, // --- End of data --- //
    computed: {
        hLastRcvd: function() {
            var msgRecvd = this.msgRecvd
            if (typeof msgRecvd === 'string') return 'Last Message Received = ' + msgRecvd
            else return 'Last Message Received = ' + this.syntaxHighlight(msgRecvd)
        },
        hLastSent: function() {
            var msgSent = this.msgSent
            if (typeof msgSent === 'string') return 'Last Message Sent = ' + msgSent
            else return 'Last Message Sent = ' + this.syntaxHighlight(msgSent)
        },
        hLastCtrlRcvd: function() {
            var msgCtrl = this.msgCtrl
            if (typeof msgCtrl === 'string') return 'Last Control Message Received = ' + msgCtrl
            else return 'Last Control Message Received = ' + this.syntaxHighlight(msgCtrl)
        },
        hLastCtrlSent: function() {
            var msgCtrlSent = this.msgCtrlSent
            if (typeof msgCtrlSent === 'string') return 'Last Control Message Sent = ' + msgCtrlSent
            //else return 'Last Message Sent = ' + this.callMethod('syntaxHighlight', [msgCtrlSent])
            else return 'Last Control Message Sent = ' + this.syntaxHighlight(msgCtrlSent)
        },
    }, // --- End of computed --- //
    methods: {
        increment: function(event) {
            console.log('Button Pressed. Event Data: ', event)

            // Increment the count by one
            this.counterBtn = this.counterBtn + 1
            var topic = this.msgRecvd.topic || 'uibuilder/vue'
            uibuilder.send( {
                'topic': topic,
                'payload': {
                    'type': 'counterBtn',
                    'btnCount': this.counterBtn,
                    'message': this.inputText,
                    'inputChkBox': this.inputChkBox
                }
            } )

        }, // --- End of increment --- //

        doLogon: function() {
            uibuilder.logon( {
                'id': this.inputId,
            } )
        }, // --- End of doLogon --- //

        doLogoff: function() {
            uibuilder.logoff()
        }, // --- End of doLogon --- //

        // return formatted HTML version of JSON object
        syntaxHighlight: function(json) {
            json = JSON.stringify(json, undefined, 4)
            json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
            json = json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
                var cls = 'number'
                if (/^"/.test(match)) {
                    if (/:$/.test(match)) {
                        cls = 'key'
                    } else {
                        cls = 'string'
                    }
                } else if (/true|false/.test(match)) {
                    cls = 'boolean'
                } else if (/null/.test(match)) {
                    cls = 'null'
                }
                return '<span class="' + cls + '">' + match + '</span>'
            })
            return json
        }, // --- End of syntaxHighlight --- //
    }, // --- End of methods --- //

    // Available hooks: beforeCreate,created,beforeMount,mounted,beforeUpdate,updated,beforeDestroy,destroyed, activated,deactivated, errorCaptured

    /** Called after the Vue app has been created. A good place to put startup code */
    created: function() {
        // Example of retrieving data from uibuilder
        this.feVersion = uibuilder.get('version')

        /** **REQUIRED** Start uibuilder comms with Node-RED @since v2.0.0-dev3
         * Pass the namespace and ioPath variables if hosting page is not in the instance root folder
         * e.g. If you get continual `uibuilderfe:ioSetup: SOCKET CONNECT ERROR` error messages.
         * e.g. uibuilder.start('/uib', '/uibuilder/vendor/socket.io') // change to use your paths/names
         * @param {Object=|string=} namespace Optional. Object containing ref to vueApp, Object containing settings, or String IO Namespace override. changes self.ioNamespace from the default.
         * @param {string=} ioPath Optional. changes self.ioPath from the default
         * @param {Object=} vueApp Optional. Reference to the VueJS instance. Used for Vue extensions.
         */
        uibuilder.start(this) // Single param passing vue app to allow Vue extensions to be used.

        //console.log(this)
    },

    /** Called once all Vue component instances have been loaded and the virtual DOM built */
    mounted: function(){
        //console.debug('[indexjs:Vue.mounted] app mounted - setting up uibuilder watchers')
        
        var app = this  // Reference to `this` in case we need it for more complex functions

        app.player = videojs('video-player')
        console.log(app.player)

        // If msg changes - msg is updated when a standard msg is received from Node-RED over Socket.IO
        // newVal relates to the attribute being listened to.
        uibuilder.onChange('msg', function(msg){
            //console.info('[indexjs:uibuilder.onChange] msg received from Node-RED server:', msg)
            app.msgRecvd = msg
            app.msgsReceived = uibuilder.get('msgsReceived')
            
            if ( msg.payload.src ) {
                app.player.src(msg.payload)
                app.player.ready(function() {
                    app.player.play();
                })
            }
        })

        //#region ---- Debug info, can be removed for live use ---- //

        /** You can use the following to help trace how messages flow back and forth.
         * You can then amend this processing to suite your requirements.
         */

        // If we receive a control message from Node-RED, we can get the new data here - we pass it to a Vue variable
        uibuilder.onChange('ctrlMsg', function(msg){
            //console.info('[indexjs:uibuilder.onChange:ctrlMsg] CONTROL msg received from Node-RED server:', msg)
            app.msgCtrl = msg
            app.msgsControl = uibuilder.get('msgsCtrl')
        })

        /** You probably only need these to help you understand the order of processing
         * If a message is sent back to Node-RED, we can grab a copy here if we want to
         */
        uibuilder.onChange('sentMsg', function(msg){
            //console.info('[indexjs:uibuilder.onChange:sentMsg] msg sent to Node-RED server:', msg)
            app.msgSent = msg
            app.msgsSent = uibuilder.get('msgsSent')
        })

        /** If we send a control message to Node-RED, we can get a copy of it here */
        uibuilder.onChange('sentCtrlMsg', function(msg){
            //console.info('[indexjs:uibuilder.onChange:sentCtrlMsg] Control message sent to Node-RED server:', msg)
            app.msgCtrlSent = msg
            app.msgsCtrlSent = uibuilder.get('msgsSentCtrl')
        })

        /** If Socket.IO connects/disconnects, we get true/false here */
        uibuilder.onChange('ioConnected', function(connected){
            //console.info('[indexjs:uibuilder.onChange:ioConnected] Socket.IO Connection Status Changed to:', connected)
            app.socketConnectedState = connected
        })
        /** If Server Time Offset changes */
        uibuilder.onChange('serverTimeOffset', function(serverTimeOffset){
            //console.info('[indexjs:uibuilder.onChange:serverTimeOffset] Offset of time between the browser and the server has changed to:', serverTimeOffset)
            app.serverTimeOffset = serverTimeOffset
        })

        /** If user is logged on/off */
        uibuilder.onChange('isAuthorised', function(isAuthorised){
            //console.info('[indexjs:uibuilder.onChange:isAuthorised] isAuthorised changed. User logged on?:', isAuthorised)
            //console.log('authData: ', uibuilder.get('authData'))
            //console.log('authTokenExpiry: ', uibuilder.get('authTokenExpiry'))
            app.isLoggedOn = isAuthorised
        })

        //#endregion ---- Debug info, can be removed for live use ---- //

    } // --- End of mounted hook --- //

}) // --- End of app1 --- //

// EOF

And in Video-Player.vue:

<template>
    <div>
        <video ref="videoPlayer" id="video-player" class="video-js"></video>
    </div>
</template>
...

As you can see, no more my-video
But still, I get the same error.

I just don't get how to inject <video> options from NR.
Would you or @TotallyInformation please show it to me?

One way to control the <video-player :options="videoOptions"/> which is a sub-component to the main Vue app is to give it a ref (reference) so you can select and then control that element.

<video-player ref="myVideo" :options="videoOptions"/>

Then you can comment out the lines // app.player = videojs('my-video') as these were used for creating a video player for the <video id="my-video" ... example.

Now for accessing and controlling the vue component player modify the lines in uibuilder.onChange ..

uibuilder.onChange('msg', function(msg){
            console.info('[indexjs:uibuilder.onChange] msg received from Node-RED server:', msg)
            console.log("myVideo", app.$refs.myVideo.player)   // log the nested player

            let myVideo = app.$refs.myVideo.player  // save the nested player in variable 
            app.msgRecvd = msg
            app.msgsReceived = uibuilder.get('msgsReceived')
            
            if ( msg.payload.src ) {   
                myVideo.src(msg.payload)   // change src for player
                myVideo.ready(function() {
                    myVideo.play();   // play
                })
            }
        })
1 Like

Thank you very much @UnborN !
As I said, I'm learning Vue using uibuilder :slight_smile:
You spotted on the mechanic that is still blurry for me.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.