Switch from hls.js to video.js for better live ui

This commit is contained in:
Adam Goldsmith 2023-01-12 01:28:48 -05:00
parent 6c53556c27
commit 2782a02954
4 changed files with 210 additions and 46 deletions

View File

@ -3,6 +3,7 @@
"version": "0.0.0",
"type": "module",
"devDependencies": {
"@types/video.js": "^7.3.50",
"@vitejs/plugin-vue": "^3.2.0",
"sass": "^1.57.1",
"typescript": "^4.9.4",
@ -12,8 +13,8 @@
"dependencies": {
"@popperjs/core": "^2.11.6",
"bootstrap": "^5.2.3",
"hls.js": "^1.2.9",
"pretty-ms": "^8.0.0",
"video.js": "^7.20.3",
"vue": "^3.2.45"
},
"scripts": {

View File

@ -2,12 +2,13 @@ lockfileVersion: 5.4
specifiers:
'@popperjs/core': ^2.11.6
'@types/video.js': ^7.3.50
'@vitejs/plugin-vue': ^3.2.0
bootstrap: ^5.2.3
hls.js: ^1.2.9
pretty-ms: ^8.0.0
sass: ^1.57.1
typescript: ^4.9.4
video.js: ^7.20.3
vite: ^3.2.5
vue: ^3.2.45
vue-tsc: ^1.0.24
@ -15,11 +16,12 @@ specifiers:
dependencies:
'@popperjs/core': 2.11.6
bootstrap: 5.2.3_@popperjs+core@2.11.6
hls.js: 1.2.9
pretty-ms: 8.0.0
video.js: 7.20.3
vue: 3.2.45
devDependencies:
'@types/video.js': 7.3.50
'@vitejs/plugin-vue': 3.2.0_vite@3.2.5+vue@3.2.45
sass: 1.57.1
typescript: 4.9.4
@ -43,6 +45,13 @@ packages:
dependencies:
'@babel/types': 7.20.7
/@babel/runtime/7.20.7:
resolution: {integrity: sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==}
engines: {node: '>=6.9.0'}
dependencies:
regenerator-runtime: 0.13.11
dev: false
/@babel/types/7.20.7:
resolution: {integrity: sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==}
engines: {node: '>=6.9.0'}
@ -73,6 +82,43 @@ packages:
resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==}
dev: false
/@types/video.js/7.3.50:
resolution: {integrity: sha512-xG0xoeyLGuWhtWMBBLRVhTEOfT2n6AjhNoWhFWVbpa6A8hSMi4eNvttuHYXsn6NslITu7IUdKPDRQ2bAWgXKDA==}
dev: true
/@videojs/http-streaming/2.14.3_video.js@7.20.3:
resolution: {integrity: sha512-2tFwxCaNbcEZzQugWf8EERwNMyNtspfHnvxRGRABQs09W/5SqmkWFuGWfUAm4wQKlXGfdPyAJ1338ASl459xAA==}
engines: {node: '>=8', npm: '>=5'}
peerDependencies:
video.js: ^6 || ^7
dependencies:
'@babel/runtime': 7.20.7
'@videojs/vhs-utils': 3.0.5
aes-decrypter: 3.1.3
global: 4.4.0
m3u8-parser: 4.7.1
mpd-parser: 0.21.1
mux.js: 6.0.1
video.js: 7.20.3
dev: false
/@videojs/vhs-utils/3.0.5:
resolution: {integrity: sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==}
engines: {node: '>=8', npm: '>=5'}
dependencies:
'@babel/runtime': 7.20.7
global: 4.4.0
url-toolkit: 2.2.5
dev: false
/@videojs/xhr/2.6.0:
resolution: {integrity: sha512-7J361GiN1tXpm+gd0xz2QWr3xNWBE+rytvo8J3KuggFaLg+U37gZQ2BuPLcnkfGffy2e+ozY70RHC8jt7zjA6Q==}
dependencies:
'@babel/runtime': 7.20.7
global: 4.4.0
is-function: 1.0.2
dev: false
/@vitejs/plugin-vue/3.2.0_vite@3.2.5+vue@3.2.45:
resolution: {integrity: sha512-E0tnaL4fr+qkdCNxJ+Xd0yM31UwMkQje76fsDVBBUCoGOUPexu2VDUYHL8P4CwV+zMvWw6nlRw19OnRKmYAJpw==}
engines: {node: ^14.18.0 || >=16.0.0}
@ -196,6 +242,20 @@ packages:
/@vue/shared/3.2.45:
resolution: {integrity: sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==}
/@xmldom/xmldom/0.7.9:
resolution: {integrity: sha512-yceMpm/xd4W2a85iqZyO09gTnHvXF6pyiWjD2jcOJs7hRoZtNNOO1eJlhHj1ixA+xip2hOyGn+LgcvLCMo5zXA==}
engines: {node: '>=10.0.0'}
dev: false
/aes-decrypter/3.1.3:
resolution: {integrity: sha512-VkG9g4BbhMBy+N5/XodDeV6F02chEk9IpgRTq/0bS80y4dzy79VH2Gtms02VXomf3HmyRe3yyJYkJ990ns+d6A==}
dependencies:
'@babel/runtime': 7.20.7
'@videojs/vhs-utils': 3.0.5
global: 4.4.0
pkcs7: 1.0.4
dev: false
/anymatch/3.1.3:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'}
@ -256,6 +316,10 @@ packages:
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
dev: true
/dom-walk/0.1.2:
resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==}
dev: false
/esbuild-android-64/0.15.18:
resolution: {integrity: sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==}
engines: {node: '>=12'}
@ -495,6 +559,13 @@ packages:
is-glob: 4.0.3
dev: true
/global/4.4.0:
resolution: {integrity: sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==}
dependencies:
min-document: 2.19.0
process: 0.11.10
dev: false
/has/1.0.3:
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
engines: {node: '>= 0.4.0'}
@ -507,14 +578,14 @@ packages:
hasBin: true
dev: true
/hls.js/1.2.9:
resolution: {integrity: sha512-SPjm8ix0xe6cYzwDvdVGh2QvQPDkCYrGWpZu6bRaKNNVyEGWM9uF0pooh/Lqj/g8QBQgPFEx1vHzW8SyMY9rqg==}
dev: false
/immutable/4.2.2:
resolution: {integrity: sha512-fTMKDwtbvO5tldky9QZ2fMX7slR0mYpY5nbnFWYp0fOzDhHqhgIw9KoYgxLWsoNTS9ZHGauHj18DTyEw6BK3Og==}
dev: true
/individual/2.0.0:
resolution: {integrity: sha512-pWt8hBCqJsUWI/HtcfWod7+N9SgAqyPEaF7JQjwzjn5vGrpg6aQ5qeAFQ7dx//UH4J1O+7xqew+gCeeFt6xN/g==}
dev: false
/is-binary-path/2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
@ -533,6 +604,10 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/is-function/1.0.2:
resolution: {integrity: sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==}
dev: false
/is-glob/4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
@ -545,11 +620,29 @@ packages:
engines: {node: '>=0.12.0'}
dev: true
/keycode/2.2.1:
resolution: {integrity: sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg==}
dev: false
/m3u8-parser/4.7.1:
resolution: {integrity: sha512-pbrQwiMiq+MmI9bl7UjtPT3AK603PV9bogNlr83uC+X9IoxqL5E4k7kU7fMQ0dpRgxgeSMygqUa0IMLQNXLBNA==}
dependencies:
'@babel/runtime': 7.20.7
'@videojs/vhs-utils': 3.0.5
global: 4.4.0
dev: false
/magic-string/0.25.9:
resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
dependencies:
sourcemap-codec: 1.4.8
/min-document/2.19.0:
resolution: {integrity: sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==}
dependencies:
dom-walk: 0.1.2
dev: false
/minimatch/5.1.2:
resolution: {integrity: sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==}
engines: {node: '>=10'}
@ -557,10 +650,29 @@ packages:
brace-expansion: 2.0.1
dev: true
/mpd-parser/0.21.1:
resolution: {integrity: sha512-BxlSXWbKE1n7eyEPBnTEkrzhS3PdmkkKdM1pgKbPnPOH0WFZIc0sPOWi7m0Uo3Wd2a4Or8Qf4ZbS7+ASqQ49fw==}
hasBin: true
dependencies:
'@babel/runtime': 7.20.7
'@videojs/vhs-utils': 3.0.5
'@xmldom/xmldom': 0.7.9
global: 4.4.0
dev: false
/muggle-string/0.1.0:
resolution: {integrity: sha512-Tr1knR3d2mKvvWthlk7202rywKbiOm4rVFLsfAaSIhJ6dt9o47W4S+JMtWhd/PW9Wrdew2/S2fSvhz3E2gkfEg==}
dev: true
/mux.js/6.0.1:
resolution: {integrity: sha512-22CHb59rH8pWGcPGW5Og7JngJ9s+z4XuSlYvnxhLuc58cA1WqGDQPzuG8I+sPm1/p0CdgpzVTaKW408k5DNn8w==}
engines: {node: '>=8', npm: '>=5'}
hasBin: true
dependencies:
'@babel/runtime': 7.20.7
global: 4.4.0
dev: false
/nanoid/3.3.4:
resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@ -588,6 +700,13 @@ packages:
engines: {node: '>=8.6'}
dev: true
/pkcs7/1.0.4:
resolution: {integrity: sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==}
hasBin: true
dependencies:
'@babel/runtime': 7.20.7
dev: false
/postcss/8.4.21:
resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==}
engines: {node: ^10 || ^12 || >=14}
@ -603,6 +722,11 @@ packages:
parse-ms: 3.0.0
dev: false
/process/0.11.10:
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
engines: {node: '>= 0.6.0'}
dev: false
/readdirp/3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
@ -610,6 +734,10 @@ packages:
picomatch: 2.3.1
dev: true
/regenerator-runtime/0.13.11:
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
dev: false
/resolve/1.22.1:
resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==}
hasBin: true
@ -627,6 +755,18 @@ packages:
fsevents: 2.3.2
dev: true
/rust-result/1.0.0:
resolution: {integrity: sha512-6cJzSBU+J/RJCF063onnQf0cDUOHs9uZI1oroSGnHOph+CQTIJ5Pp2hK5kEQq1+7yE/EEWfulSNXAQ2jikPthA==}
dependencies:
individual: 2.0.0
dev: false
/safe-json-parse/4.0.0:
resolution: {integrity: sha512-RjZPPHugjK0TOzFrLZ8inw44s9bKox99/0AZW9o/BEQVrJfhI+fIHMErnPyRa89/yRXUUr93q+tiN6zhoVV4wQ==}
dependencies:
rust-result: 1.0.0
dev: false
/sass/1.57.1:
resolution: {integrity: sha512-O2+LwLS79op7GI0xZ8fqzF7X2m/m8WFfI02dHOdsK5R2ECeS5F62zrwg/relM1rjSLy7Vd/DiMNIvPrQGsA0jw==}
engines: {node: '>=12.0.0'}
@ -671,6 +811,38 @@ packages:
hasBin: true
dev: true
/url-toolkit/2.2.5:
resolution: {integrity: sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg==}
dev: false
/video.js/7.20.3:
resolution: {integrity: sha512-JMspxaK74LdfWcv69XWhX4rILywz/eInOVPdKefpQiZJSMD5O8xXYueqACP2Q5yqKstycgmmEKlJzZ+kVmDciw==}
dependencies:
'@babel/runtime': 7.20.7
'@videojs/http-streaming': 2.14.3_video.js@7.20.3
'@videojs/vhs-utils': 3.0.5
'@videojs/xhr': 2.6.0
aes-decrypter: 3.1.3
global: 4.4.0
keycode: 2.2.1
m3u8-parser: 4.7.1
mpd-parser: 0.21.1
mux.js: 6.0.1
safe-json-parse: 4.0.0
videojs-font: 3.2.0
videojs-vtt.js: 0.15.4
dev: false
/videojs-font/3.2.0:
resolution: {integrity: sha512-g8vHMKK2/JGorSfqAZQUmYYNnXmfec4MLhwtEFS+mMs2IDY398GLysy6BH6K+aS1KMNu/xWZ8Sue/X/mdQPliA==}
dev: false
/videojs-vtt.js/0.15.4:
resolution: {integrity: sha512-r6IhM325fcLb1D6pgsMkTQT1PpFdUdYZa1iqk7wJEu+QlibBwATPfPc9Bg8Jiym0GE5yP1AG2rMLu+QMVWkYtA==}
dependencies:
global: 4.4.0
dev: false
/vite/3.2.5_sass@1.57.1:
resolution: {integrity: sha512-4mVEpXpSOgrssFZAOmGIr85wPHKvaDAcXqxVxVRZhljkJOMZi1ibLibzjLHzJvcok8BMguLc7g1W6W/GqZbLdQ==}
engines: {node: ^14.18.0 || >=16.0.0}

View File

@ -3,7 +3,17 @@
<h3 class="card-header" :data-color="color">
{{ name || 'Unknown' }}
</h3>
<video muted class="card-img webcam" controls autoplay ref="video"></video>
<video-js
ref="video"
class="card-img vjs-fluid"
controls
autoplay
muted
preload="auto"
>
<source :src="`/webcam/${slug}.m3u8`" />
</video-js>
<div class="card-body" v-if="status">
<div>{{ status.state.text }}</div>
<div>
@ -44,8 +54,9 @@
</template>
<script setup lang="ts">
import Hls from 'hls.js';
import { computed, onMounted, Ref, ref, watchEffect } from 'vue';
import videojs from 'video.js';
import 'video.js/dist/video-js.css';
import { computed, Ref, ref, watchEffect } from 'vue';
import prettyMilliseconds from 'pretty-ms';
import { CurrentOrHistoryPayload } from '../types/octoprint';
@ -63,34 +74,9 @@ export interface Props {
export type PrinterInfo = Omit<Props, 'slug' | 'now'>;
const props = defineProps<Props>();
const video: Ref<HTMLMediaElement | null> = ref(null);
const hls: Ref<Hls | null> = ref(null);
if (Hls.isSupported()) {
hls.value = new Hls({
liveDurationInfinity: true,
backBufferLength: 30,
manifestLoadingTimeOut: 1000,
manifestLoadingMaxRetry: 30,
manifestLoadingRetryDelay: 500,
//debug: true,
});
hls.value.on(Hls.Events.MEDIA_ATTACHED, () => {
hls.value!.loadSource(`/webcam/${props.slug}.m3u8`);
hls.value!.on(Hls.Events.MANIFEST_PARSED, (event, data) => {
video.value?.play();
console.log(
'manifest loaded, found ' + data.levels.length + ' quality level'
);
});
});
hls.value.on(Hls.Events.ERROR, (event, data) => {
console.log(data);
});
}
function formatDuration(seconds: number): string {
return prettyMilliseconds(seconds * 1000);
}
@ -111,11 +97,12 @@ const lastUpdateString = computed(() => {
});
watchEffect(() => {
console.log(video.value, hls.value);
if (hls.value && video.value) {
// if hls and video element are valid, bind them together
hls.value.attachMedia(video.value);
console.log('video and hls.js are now bound together !');
if (video.value) {
// if video element valid, bind to videojs
videojs(video.value, {
liveui: true,
liveTracker: { trackingThreshold: 0 },
});
}
});
</script>
@ -147,8 +134,4 @@ $bs-colors: ('red', 'orange', 'yellow', 'green', 'blue', 'white');
color: var(--bs-light);
}
}
.webcam {
background-color: black;
}
</style>

View File

@ -3,7 +3,15 @@ import vue from '@vitejs/plugin-vue';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: (tag) => tag == 'video-js',
},
},
}),
],
server: {
proxy: {
'/ws': {