Using Vue 2 and Video.js 7 I am trying to show a video through a conditional component (sometimes I need to show an iframe instead of videojs). The video (.m3u8 and .mp4) loads but the duration is shown as "--:-" and clicking the seek bar gives an error in the console:
VIDEOJS: TypeError: Failed to set the 'currentTime' property on 'HTMLMediaElement': The provided double value is non-finite.
at Html5.setCurrentTime (video.es.js?c063:19084:1)
at set (video.es.js?c063:9410:1)
at Player.eval (video.es.js?c063:22620:1)
at Player.ready (video.es.js?c063:3756:1)
at Player.techCall_ (video.es.js?c063:22618:1)
at Player.currentTime (video.es.js?c063:22874:1)
at SeekBar.handleMouseMove (video.es.js?c063:13106:1)
at SeekBar.handleMouseDown (video.es.js?c063:12181:1)
at SeekBar.handleMouseDown (video.es.js?c063:13055:1)
at ProgressControl.handleMouseDown (video.es.js?c063:13448:1) Video is not ready. (Video.js)
I noticed that videojs returns NaN as duration, even after the metadata loaded. Other than that I have no clue what's going wrong or how I can make it so videojs has a useable duration.
main.vue
<template>
<div>
<videoPlayer
:playerData="data.player.VIDEO_PLAYER"
ref="videoPlayer"
#playerReady="playerReady"
></videoPlayer>
</div>
</template>
<script>
import videoPlayer from "components/player";
export default {
name: "Name",
data() {
return {
playerIsReady: false,
currentVideoTime: 0,
};
},
components: {
videoPlayer,
},
methods: {
playerReady(status) {
this.playerIsReady = status;
},
checkTimePlayer() {
const self = this;
this.playerInterval = setInterval(function () {
self.currentVideoTime = self.$refs.videoPlayer
? self.$refs.videoPlayer.getCurrentTime()
: 0;
}, 500);
},
},
watch: {
playerIsReady(newVal) {
if (newVal) {
this.checkTimePlayer();
} else if (this.playerInterval) {
clearInterval(this.playerInterval);
}
},
},
};
</script>
player.vue
<template>
<div>
<videoPlayerVideojs
v-if="playerData.stream_type == 'videojs'"
:options="options"
ref="videoPlayerVideomeojs"
#playerReady="playerReady"
></videoPlayerVideojs>
<videoPlayerSomethingElse
v-else-if="playerData.stream_type == 'somethingElse'"
:options="options"
ref="videoPlayerSomethingElse"
></videoPlayerSomethingElse>
</div>
</template>
<script>
import videoPlayerVideojs from "components/playerVideojs";
import videoPlayerSomethingElse from "components/playerSomethingElse";
export default {
name: "VideoPlayer",
props: {
playerData: {
type: Object,
required: true,
},
},
components: {
videoPlayerVideojs,
videoPlayerSomethingElse,
},
data() {
return {
player: null,
options: {},
};
},
mounted() {
if (this.playerData.stream_type === "videojs") {
this.options = {
preload: "auto",
fluid: true,
autoplay: true,
controls: true,
sources: [
{
src: "https://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4",
type: "video/mp4",
},
],
};
this.player = this.$refs.videoPlayerVideojs;
} else if (this.playerData.stream_type === "somethingElse") {
// something ... else
this.player = this.$refs.videoPlayerSomethingElse;
}
},
methods: {
getCurrentTime() {
return this.player.getCurrentTime();
},
playerReady(status) {
this.$emit("playerReady", status);
},
},
};
</script>
playerVideojs.vue
<template>
<div>
<video ref="video" class="video-js"></video>
</div>
</template>
<script>
import videojs from "video.js";
import "video.js/dist/video-js.css";
export default {
name: "VideoPlayerVideojs",
props: {
options: {
type: Object,
required: true,
},
},
data() {
return {
player: null,
ready: false,
currentVideoTime: 0,
};
},
async mounted() {
const self = this;
await this.$nextTick();
this.player = videojs(
this.$refs.video,
this.options
);
this.player.on("ready", () => {
console.log("ready");
console.log(this.player.readyState());
console.log(this.player.duration());
console.log(this.player.liveTracker.seekableEnd());
});
this.player.on("loadedmetadata", () => {
console.log("loaded metadata");
self.ready = true;
console.log(this.player.readyState());
console.log(this.player.duration());
console.log(this.player.liveTracker.seekableEnd());
});
this.player.on("durationchange", () => {
console.log("duration change");
console.log(this.player.readyState());
console.log(this.player.duration());
console.log(this.player.liveTracker.seekableEnd());
});
},
watch: {
ready(newVal) {
this.$emit("playerReady", newVal);
},
},
methods: {
getCurrentTime() {
return this.player.currentTime();
},
},
beforeDestroy() {
if (this.player) {
this.player.dispose();
}
},
};
</script>
The console outputs
14:31:33.682 playerVideojs.vue?5887:40 ready
14:31:33.682 playerVideojs.vue?5887:42 0
14:31:33.682 playerVideojs.vue?5887:43 NaN
14:31:33.682 playerVideojs.vue?5887:44 Infinity
14:31:34.085 playerVideojs.vue?5887:54 duration change
14:31:34.085 playerVideojs.vue?5887:56 1
14:31:34.085 playerVideojs.vue?5887:57 NaN
14:31:34.085 playerVideojs.vue?5887:58 596.504
14:31:34.085 playerVideojs.vue?5887:47 loaded metadata
14:31:34.085 playerVideojs.vue?5887:49 1
14:31:34.085 playerVideojs.vue?5887:50 NaN
14:31:34.086 playerVideojs.vue?5887:51 596.504
Via Vue devtools I've compared the player on the Vue page with a working player on a non-Vue page. And found that player.controlBar.durationDisplay.duration is "NaN" for the Vue player and a float for the non-Vue player.
Also when using an .m3u8 I notice that player.vhs.playlists.media_.id is 2 for the Vue player and 1 for the non-Vue player. Could it be that the media I'm trying to load is the second time the player tries to load media?
Other than that the players are pretty much identical as far as I can see.
Related
I am trying to develop guided tour with shepherd: https://www.npmjs.com/package/vue-shepherd but I cannot get the element. So here is my component for guide tour:
<template>
<div></div>
</template>
<script>
import { useShepherd } from 'vue-shepherd';
export default {
props: {
element: {
required: true,
},
id: {
type: Number,
required: true,
},
title: {
type: String,
},
text: {
type: String,
required: true,
},
position: {
type: String,
required: true,
},
},
mounted() {
this.tour.start();
},
data() {
return {
tour: null,
};
},
methods: {
createTour() {
this.tour = useShepherd({
useModalOverlay: true,
});
this.tour.addStep({
title: this.title,
text: this.text,
attachTo: { element: this.element, on: this.position },
buttons: [
{
action() {
return this.back();
},
classes: 'shepherd-button-secondary',
text: 'Back',
},
{
action() {
return this.next();
},
text: 'Next',
},
],
id: this.id,
});
this.tour.start();
},
},
created() {
this.createTour();
},
};
</script>
and here is my parent component:
<button ref="button">
Click
</button>
<guide :element="element" :title="'Tour'" :text="'Example'" :position="'bottom'" :id="1" />
and the mounted of the parent element:
mounted() {
this.element = this.$refs.button;
},
but the tour doesnt attach the the button element. it just appears in the middle of the page. Why do you think it is?
Looks like a usage problem in vue hooks. The child component's hooks fire before the parent component's hooks. Therefore, at the time of the creation of the tour, the element does not exist. Vue-shepherd does not use vue reactivity.
Use
mounted() {
this.$nextTick(() => {
this.createTour();
});
},
Codesanbox
But it's better to change the component structure. If you are using vue3 you can use my package
In this case it will look like this
<template>
<button v-tour-step:1="step1">
Click
</button>
</template>
<script>
import { defineComponent, inject, onMounted } from "vue";
export default defineComponent({
setup() {
const tour = inject("myTour");
onMounted(() => {
tour.start();
});
const step1 = {
/* your step options */
}
return {
step1,
};
}
});
</script>
I want to use vue-meta to import a 3rd party SwiperJS script into my nuxt website.
The script is loaded only after mounted()-hook is called so I also initialize it on update(). This kind of works but the v-if on the wrapper does not update.
<template>
<section
class="swiper"
v-if="isSwiperLoaded"
>
<div class="swiper-wrapper"> ... </div>
</section>
</template>
<script>
export default {
data() {
return { swiperLoaded: false, swiperInitialized: false }
},
computed: {
isSwiperLoaded: {
get() {
return this.swiperLoaded
},
set(value) {
this.swiperLoaded = value
},
},
isSwiperInitialized: {
get() {
return this.swiperInitialized
},
set(value) {
this.swiperInitialized = value
},
},
},
head() {
return {
script: [
{
hid: 'swiper',
src: 'https://cdn.jsdelivr.net/npm/swiper#8/swiper-bundle.min.js',
defer: true,
// Changed after script load
callback: () => {
this.isSwiperLoaded = true
},
},
],
link: [
{
rel: 'stylesheet',
type: 'text/css',
href: 'https://cdn.jsdelivr.net/npm/swiper#8/swiper-bundle.min.css',
},
],
}
},
methods: {
initSwiper() {
const swiperOptions = {
...
}
let swiper = new Swiper(this.$el, swiperOptions)
},
},
mounted() {
if (!this.isSwiperInitialized && this.isSwiperLoaded) {
console.log('INIT LOADED')
this.initSwiper()
this.isSwiperInitialized = true
}
},
updated() {
if (!this.isSwiperInitialized && this.isSwiperLoaded) {
console.log('UPD LOADED')
this.initSwiper()
this.isSwiperInitialized = true
}
},
}
</script>
Also I noticed the computed values are normally for getting and setting values from store and not local values. Maybe there is an easier way of updating the variables.
I have button which toggle data passed to this ChartComponent. If hold any seconds after toggle and retoggle so its work but if we doing it a bit fast, it is cause
Cannot read properties of null (reading 'getContext')? error.
<template>
<canvas></canvas>
</template>
<script>
import { defineComponent } from 'vue'
import Chart from 'chart.js/auto'
export default defineComponent({
name: 'ChartComponent',
props: {
type: {
type: String,
required: true,
},
data: {
type: Object,
required: true,
},
options: {
type: Object,
default: () => ({}),
},
},
data: () => ({
chart: null,
}),
watch: {
data: {
handler() {
this.chart.destroy()
this.renderChart()
},
deep: true,
},
},
mounted() {
this.renderChart()
},
methods: {
renderChart() {
this.chart = new Chart(this.$el, {
type: this.type,
data: this.data,
options: this.options,
})
},
},
})
</script>
You should unwrap/unproxy this.chart before accessing it by using Vue3 "toRaw" function:
import { toRaw } from "vue";
...
watch: {
data: {
handler() {
toRaw(this.chart).destroy()
this.renderChart()
},
I'm trying to add the paypal sdk via vue-head(https://www.npmjs.com/package/vue-head) in my component but I keep getting this error:
Error in mounted hook: "ReferenceError: paypal is not defined"
What am I doing wrong here? Is the SDK simply not loading before mounted?
Is there a better way to accomplish this? Does anyone have an example of their paypal implementation in vue? Any help would be greatly appreciated.
edit: Also if I include the script tag server side (rails) then try to access paypal in vue I see this error:
Could not find driver for framework: [object Object]
<template>
<div id="paypal-button" />
</template>
<script>
import { mapState as mapConfigState } from '../scripts/store/appConfig';
export default {
props: {
totalPrice: {
type: String,
required: true,
},
currency: {
type: String,
required: true,
'default': 'USD',
},
buttonStyle: {
type: Object,
required: false,
},
},
computed: {
...mapConfigState({
customer: state => state.customer,
}),
paypalEnvironment() {
return (this.customer.paypalTestingMode) ? 'sandbox' : 'production';
},
client() {
return {
sandbox: this.customer.paypalClientIdSandbox,
production: this.customer.paypalClientIdLIVE,
};
},
},
head: {
script() {
return [
{
type: 'text/javascript',
src: `https://www.paypal.com/sdk/js?client-id=${this.client[this.paypalEnvironment]}`,
},
];
},
},
mounted() {
const total = this.totalPrice;
const currency = this.currency;
paypal.Buttons.driver(
{
env: this.paypalEnvironment,
client: this.client,
style: this.buttonStyle,
createOrder(data, actions) {
return actions.order.create({
purchase_units: [
{
amount: {
value: total,
currency,
},
},
],
});
},
onApprove(data, actions) {
return actions.order.capture();
},
}, '#paypal-button'
);
},
};
</script>
edit2: I tried adding the script in my mounted hook like this:
let el = document.querySelector(`script[src="https://www.paypal.com/sdk/js?client-id=${this.client[this.paypalEnvironment]}"]`);
if (!el) {
const src = `https://www.paypal.com/sdk/js?client-id=${this.client[this.paypalEnvironment]}`;
el = document.createElement('script');
el.type = 'text/javascript';
el.async = true;
el.src = src;
document.head.appendChild(el);
}
I can see the script in the head tag in the dev console but paypal still is not defined.
For anyone else who is trying to implement PayPal in a Vue component:
<template>
<div id="paypal-button" />
</template>
<script>
export default {
mounted() {
function loadScript(url, callback) {
const el = document.querySelector(`script[src="${url}"]`);
if (!el) {
const s = document.createElement('script');
s.setAttribute('src', url); s.onload = callback;
document.head.insertBefore(s, document.head.firstElementChild);
}
}
loadScript('https://www.paypal.com/sdk/js?client-id=sb¤cy=USD', () => {
paypal.Buttons({
// Set up the transaction
createOrder(data, actions) {
return actions.order.create({
purchase_units: [{
amount: {
value: '0.01',
},
}],
});
},
// Finalize the transaction
onApprove(data, actions) {
return actions.order.capture().then(details => {
// Show a success message to the buyer
alert(`Transaction completed by ${details.payer.name.given_name}`);
});
},
}).render('#paypal-button');
});
},
};
</script>
Alternatively you can use this: https://github.com/paypal/paypal-js
Im trying to display chart using chartjs by calling API, but unable to do so.
Here s my LineChart.vue:
<script>
import {Line, mixins} from 'vue-chartjs' // We specify what type of chart we want from vue-chartjs and the mixins module
const { reactiveProp } = mixins
export default { //We are extending the base chart class as mentioned above
extends: Line,
mixins: [reactiveProp],
data () {
return {
options: { //chart options
lineTension: 0,
maintainAspectRatio: false,
legend: {
display: false
},
scales: {
yAxes: [
{
scaleLabel: {
display: false
},
ticks: {
beginAtZero: true,
// eslint-disable-next-line no-unused-vars
callback (value, index, values) {
return `${value }%`
}
}
}
],
xAxes: [
{
type: 'time',
time: {
unit: 'month'
},
scaleLabel: {
display: true,
labelString: ''
}
}
]
}
}
}
},
mounted () {
// this.chartData is created in the mixin
this.renderChart(this.chartData, this.options)
}
}
</script>
And here is my Home.vue where i have imported the LineChart:
<template>
<div class="chart">
<line-chart :chart-data="datacollection"></line-chart>
</div>
</template>
<script>
import LineChart from './LineChart'
import axios from 'axios'
import DateTime from 'luxon'
export default {
data () {
return {
date: {},
challenge: {},
datacollection: {}
}
},
component: {LineChart},
created() {
this.fillData()
},
mounted () {
this.fillData()
},
methods: {
fillData () {
axios.get('https://my_api_goes_here')
.then(response => {
const results = response.data
const dateresult = results.map(a => a.date)
const challengeresult = results.map(a => a.challenge)
this.date = dateresult
this.challenge = challengeresult
this.datacollection = {
labels: [this.date].map(labels => DateTime.fromMillis(labels * 1000).toFormat('MMM yyyy')),
datasets: [
{
data: [this.challenge],
label: 'Africa',
borderColor: '#7367F0'
}
]
}
})
.catch(error => {
console.log(error)
})
}
}
}
</script>
Dont know why the chart did not appear even though my other resources have been loaded from the API call, when i checked out my console, this is what error im getting:
TypeError: results.map is not a function
Please check out my logic and let me where the error is.