-
-
Notifications
You must be signed in to change notification settings - Fork 88
Using Video.js with uibuilder and VueJS
VideoJS is a browser embedded HTML5 video player.
You can easily use it with uibuilder with or without VueJS. However, the documentation for VideoJS makes a LOT of assumptions which isn't helpful if you are not already an expert.
Here then is a working example to help you get going.
In this example, I show two different ways to load the player. Directly - which is by far the simplest and recommended.
The second method is to wrap it in a VueJS Component. Really, unless you want to do lots of clever things and want to be able to do that consistently across multiple uses, a component is likely to be overkill.
For the direct method, I put the player into the html without a video loaded. In the function that processes incoming msg's from Node-RED, I load the video from a URL and then start the player once the video has loaded.
If you filter out all of the code that is the standard uibuilder Vue/bootstrap template, you will see that the whole thing needs about a dozen lines of code. Only 1 line of HTML (plus loading the external CSS and scripts), and in the js file, 1 data variable and 3 actual lines of code.
For the component method, all I've done is adjusted the vue component example from the videojs docs site so that it works with http-vue-loader so that no build step is required. It is up to you, the reader, to make that component into something useful.
Load the flow into Node-RED, you need uibuilder installed of course. Deploy then edit the source code to use the files given below.
Note that I've only use the CDN versions of the player in this example. It is recommended to use the uibuilder library manager to install VideoJS via npm then change the references to use the installed version. The alternative code is included below (commented out).
[{"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":[]}]
<!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" />
<!-- You can use a version of video.js from an external CDN -->
<link type="text/css" rel="stylesheet" href="https://vjs.zencdn.net/7.10.2/video-js.css" />
<!-- OR use the locally installed version after installing using the uibuilder library manager -->
<!-- <link type="text/css" rel="stylesheet" href="../uibuilder/vendor/video.js/dist/video-js.min.css" /> -->
<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>
<!-- Only needed if using the .vue module -->
<script src="https://unpkg.com/http-vue-loader"></script>
<!-- You can use a version of video.js from an external CDN -->
<script src="https://vjs.zencdn.net/7.10.2/video.js"></script>
<!-- OR use the locally installed version after installing using the uibuilder library manager -->
<!-- <script src="../uibuilder/vendor/video.js/dist/video.min.js"></script> -->
<script src="./uibuilderfe.js"></script>
<script src="./index.js"></script>
</body></html>
'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, '&').replace(/</g, '<').replace(/>/g, '>')
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
<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>
Please feel free to add comments to the page (clearly mark with your initials & please add a commit msg so we know what has changed). You can contact me in the Discourse forum, or raise an issue here in GitHub! I will make sure all comments & suggestions are represented here.
-
Walkthrough 🔗 Getting started
-
In Progress and To Do 🔗 What's coming up for uibuilder?
-
Awesome uibuilder Examples, tutorials, templates and references.
-
How To
- How to send data when a client connects or reloads the page
- Send messages to a specific client
- Cache & Replay Messages
- Cache without a helper node
- Use webpack to optimise front-end libraries and code
- How to contribute & coding standards
- How to use NGINX as a proxy for Node-RED
- How to manage packages manually
- How to upload a file from the browser to Node-RED
-
Vanilla HTML/JavaScript examples
-
VueJS general hints, tips and examples
- Load Vue (v2 or v3) components without a build step (modern browsers only)
- How to use webpack with VueJS (or other frameworks)
- Awesome VueJS - Tips, info & libraries for working with Vue
- Components that work
-
VueJS v3 hints, tips and examples
-
VueJS v2 hints, tips and examples
- Dynamically load .vue files without a build step (Vue v2)
- Really Simple Example (Quote of the Day)
- Example charts using Chartkick, Chart.js, Google
- Example Gauge using vue-svg-gauge
- Example charts using ApexCharts
- Example chart using Vue-ECharts
- Example: debug messages using uibuilder & Vue
- Example: knob/gauge widget for uibuilder & Vue
- Example: Embedded video player using VideoJS
- Simple Button Acknowledgement Example Thanks to ringmybell
- Using Vue-Router without a build step Thanks to AFelix
- Vue Canvas Knob Component Thanks to Klaus Zerbe
-
Examples for other frameworks (check version before trying)
- Basic jQuery example - Updated for uibuilder v6.1
- ReactJS with no build - updated for uibuilder v5/6
-
Examples for other frameworks (may not work, out-of-date)
-
Outdated Pages (Historic only)
- v1 Examples (these need updating to uibuilder v2/v3/v4/v5)