Vue VideoJS in UIBuilder

The flow on its own wouldn't help because it doesn't include the source code for the index.(html|js) nor the .vue file.

Here is a working example with the player loaded directly with no video. And an inject that sends the video url then starts the playback when the video has loaded - all standard code taken from the videojs website and adjusted into the standard uibuilder template.

It also has a working example of the .vue file dynamically loaded via the http loader. I haven't bothered to do anything with it though as I don't think that a vue component is warranted unless you want to do more complex things.

[{"id":"dfcd2046.f0d35","type":"inject","z":"ff1a7711.244f48","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"src\": \"//vjs.zencdn.net/v/oceans.mp4\", \"type\": \"video/mp4\"}","payloadType":"json","x":130,"y":1060,"wires":[["de3ed4a5.ddb688"]]},{"id":"de3ed4a5.ddb688","type":"uibuilder","z":"ff1a7711.244f48","name":"","topic":"","url":"videojs","fwdInMessages":false,"allowScripts":false,"allowStyles":false,"copyIndex":true,"showfolder":false,"useSecurity":false,"sessionLength":432000,"tokenAutoExtend":false,"x":330,"y":1060,"wires":[["b9248f71.f5d22"],["b3f325aa.1ea888"]]},{"id":"b9248f71.f5d22","type":"debug","z":"ff1a7711.244f48","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":630,"y":1040,"wires":[]},{"id":"b3f325aa.1ea888","type":"debug","z":"ff1a7711.244f48","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":630,"y":1080,"wires":[]}]

index.html

<!doctype html><html lang="en"><head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Node-RED UI Builder - Video.JS Tests</title>
    <meta name="description" content="Node-RED UI Builder - VueJS + bootstrap-vue default template">

    <link rel="icon" href="./images/node-blue.ico">

    <link type="text/css" rel="stylesheet" href="../uibuilder/vendor/bootstrap/dist/css/bootstrap.min.css" />
    <link type="text/css" rel="stylesheet" href="../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.css" />
    <link href="https://vjs.zencdn.net/7.10.2/video-js.css" rel="stylesheet" />
    <link type="text/css" rel="stylesheet" href="./index.css" media="all">

</head><body>

    <div id="app" v-cloak>
        <b-container id="app_container">

            <div>
                <video  id="my-video"
                        class="video-js"
                        controls
                        preload="auto"
                        width="640"
                        height="264"
                        data-setup="{}">
                    <!-- <source src="//vjs.zencdn.net/v/oceans.mp4" type="video/mp4"> -->
                </video>
                <hr>
                <video-player :options="videoOptions"/>
            </div>

            <h2>Dynamic Data</h2>
            <div>
                <p>Uses Vue to dynamically update in response to messages from Node-RED.</p>
                <p>
                    Check out the <code>mounted</code> function in <code>index.js</code> to See
                    how easy it is to update Vue data from Node-RED.
                </p>

                <b-card class="mt-3" header="Status" border-variant="info" header-bg-variant="info" header-text-variant="white" align="center" >
                    <p class="float-left">Socket.io Connection Status: <b>{{socketConnectedState}}</b></p>
                    <p class="float-right">Time offset between browser and server: <b>{{serverTimeOffset}}</b> hours</p>
                </b-card>

                <b-card class="mt-3" header="Normal Messages" border-variant="primary" header-bg-variant="primary" header-text-variant="white" align="left" >
                    <p>
                        Messages: Received=<b>{{msgsReceived}}</b>, Sent=<b>{{msgsSent}}</b>
                    </p>
                    <pre v-html="hLastRcvd" class="syntax-highlight"></pre>
                    <pre v-html="hLastSent" class="syntax-highlight"></pre>
                    <p slot="footer" class="mb-0">
                        The received message is from the input to the uibuilder node.
                        The send message will appear out of port #1 of the node.
                    </p>
                </b-card>

                <b-card class="mt-3" header="Control Messages" border-variant="secondary" header-bg-variant="secondary" header-text-variant="white" align="left" >
                    <p>
                        Control Messages: Received=<b>{{msgsControl}}</b>, Sent=<b>{{msgsCtrlSent}}</b>
                    </p>
                    <pre v-html="hLastCtrlRcvd" class="syntax-highlight"></pre>
                    <pre v-html="hLastCtrlSent" class="syntax-highlight"></pre>
                    <p slot="footer" class="mb-0">
                        Control messages always appear out of port #2 of the uibuilder node
                        whether they are from the server or the client. The <code>from</code> property
                        of the message tells you where it came from.
                    </p>
                </b-card>
            </div>

        </b-container>
    </div>

    <script src="../uibuilder/vendor/socket.io/socket.io.js"></script>
    <script src="../uibuilder/vendor/vue/dist/vue.js"></script>
    <script src="../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.js"></script>
    <script src="https://unpkg.com/http-vue-loader"></script>
    <script src="https://vjs.zencdn.net/7.10.2/video.js"></script>
    <script src="./uibuilderfe.js"></script>
    <script src="./index.js"></script>

</body></html>

index.js

'use strict'

new Vue({
    el: '#app',
    
    components: {
        'video-player': httpVueLoader('video-player.vue'),
    }, // --- End of components --- //
    
    data: {
        player: null,
        videoOptions: {
			autoplay: false,
			controls: true,
			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('my-video')
        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

video-player.vue

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

<script>
    //import videojs from 'video.js';
    
    //export default {
    module.exports = {
        name: "VideoPlayer",
        props: {
            options: {
                type: Object,
                default() {
                    return {};
                }
            }
        },
        data() {
            return {
                player: null
            }
        },
        mounted() {
            this.player = videojs(this.$refs.videoPlayer, this.options, function onPlayerReady() {
                console.log('onPlayerReady', this);
            })
        },
        beforeDestroy() {
            if (this.player) {
                this.player.dispose()
            }
        }
    }
</script>