I am working on a small scale app that displays videos in multiple ways using a video-player component.
Currently I am implementing a stack-list, which is a container that holds video-stack components, and each stack contains one or more video-player components.
While the correct videos are loaded from the DOM, there is a noticeable multi-second lag (in terms of keyboard response) which seems to be related to the ending of the currently played video and the fetching of the next video in the stack.
How can I get rid of this lag? Videos are able to be toggled/selected via mouse hovers or WASD keyboard commands (A: previous, D: next), and the lag can cause a delay in keyboard inputs being registered.
video-stack.hbs
{{video-player highlightedStyle=(string-append stackStyle borderStyle)
looping=(is-single-video videos) videoPos=selectedVidPos
isMuted=(if (video-selected key selectedStackIndex) isMuted true)
url=(if curVideo.teaser.isUrl curVideo.teaser.fileIdentifier
(make-local-url modelIdentifier curVideo.teaser.fileIdentifier))
onClickCallback=(action 'stackClicked')
onHoverCallback=(action 'stackHovered')
onEndedCallback=(action 'getNextVid')}}
video-stack.js
import Ember from 'ember';
export default Ember.Component.extend({
selectedVidPos: 0,
selectedStackIndex: 0,
stackStyle: '',
playerSize: '',
isMuted: true,
init() {
this._super(...arguments);
switch(this.get('videos').length){
case 1:
break;
case 2:
this.set('stackStyle', 'vid-shadows--2');
break;
case 3:
this.set('stackStyle', 'vid-shadows--3');
break;
case 4:
this.set('stackStyle', 'vid-shadows--4');
break;
default:
this.set('stackStyle', 'vid-shadows--4');
break;
}
},
curVideo: Ember.computed('videos', 'selectedVidPos', function () {
return this.get('videos')[this.get('selectedVidPos')];
}),
actions: {
stackClicked() {
this.get('onClickCallback') (this.get('videos'), this.get('selectedVidPos'));
},
getNextVid() {
let arrayLength = this.get('videos').length;
//check if there is only 1 video in the stack
if (arrayLength === 1) {
return;
}
let curArrayPos = parseInt(this.get('selectedVidPos'));
this.set('selectedVidPos', (curArrayPos + 1) % arrayLength);
},
stackHovered() {
this.get('onHoverCallback') (this.get('videos'), this.get('selectedStackIndex'));
}
}
});
video-player.hbs
<video oncanplay={{action 'play'}} looping=true
onended={{action 'ended'}} src={{url}}
class="video-player__video {{highlightedStyle}} {{if playing '' 'video-
player__darken'}}" muted={{muted}} />
video-player.js
import Ember from 'ember';
export default Ember.Component.extend({
url: null,
looping: false,
playing: true,
muted: true,
highlightedStyle: '',
click(event) {
this.get('onClickCallback') (this.get('videoPos'));
event.stopPropagation();
},
mouseEnter() {
this.get('onHoverCallback') (this.get('videoPos'));
},
willClearRender() {
this.set('playingObserver', null);
this.set('urlObserver', null);
},
playingObserver: Ember.observer('playing', function() {
if (this) {
var p = this.get("playing");
var videoElement = this.$().find("video").get(0);
if (videoElement) {
if (p) {
videoElement.play();
}
else {
videoElement.pause();
}
}
else {
console.log("No video element found!");
}
}
}),
urlObserver: Ember.observer('url', function() {
if (this) {
var videoElement = this.$().find("video").get(0);
if (videoElement) {
videoElement.load();
}
else {
console.log("No video element found");
}
}
}),
actions: {
ended() {
if (this.get('looping')) {
this.$().find("video").get(0).play();
console.log("video-player ended");
}
else {
console.log(this.get('videoPos'));
this.get('onEndedCallback') (this.get('videoPos'));
}
},
play() {
if (this.get('playing')) {
this.$().find("video").get(0).play();
}
}
}
});
I can post more code if it would help shed light on the culprit, thanks!
I found the culprit of the lag. The issue was in the parent container, content-area.js, which had a resetTimeout action that was being called incorrectly, which caused the focus to cycle needlessly, resulting in the lag.
Also implemented a switch off in terms of rendering videos to ensure smooth loading from one video to the next in video-stack.js, there are now 2 video objects, A & B, which are fetched and preloaded from the blob object, showing one while the other is hidden. Once the displayed video ends, they swap out, and the next video in the stack is loaded.
video-stack.js
export default Ember.Component.extend({
selectedVidAPos: 0,
selectedVidBPos: 0,
selectedStackIndex: 0,
stackStyle: '',
playerSize: '',
isMuted: true,
showVidA: true,
init() {
...
}
},
videoA: Ember.computed('videos', 'selectedVidAPos', function () {
return this.get('videos')[this.get('selectedVidAPos')];
}),
videoB: Ember.computed('videos', 'selectedVidBPos', function () {
return this.get('videos')[this.get('selectedVidBPos')];
}),
actions: {
stackClicked() {
this.get('onClickCallback') (this.get('videos'), (this.get('showVidA') ? this.get('selectedVidAPos') : this.get('selectedVidBPos')));
},
getNextVideoA() {
let arrayLength = this.get('videos').length;
if (arrayLength === 1) {
return;
}
let curArrayPos = parseInt(this.get('selectedVidAPos'));
this.set('selectedVidAPos', (curArrayPos + 2) % arrayLength);
this.set('showVidA', false);
},
getNextVideoB(){
let arrayLength = this.get('videos').length;
if (arrayLength === 1) {
return;
}
let curArrayPos = parseInt(this.get('selectedVidBPos'));
this.set('selectedVidBPos', (curArrayPos + 2) % arrayLength);
this.set('showVidA', true);
},
stackHovered() {
this.get('onHoverCallback') (this.get('videos'), this.get('selectedStackIndex'));
}
}
});
content-area.js
import Ember from 'ember';
import KeyboardControls from '../mixins/keyboard-controls';
export default Ember.Component.extend(KeyboardControls, {
displayVideoSelect: false,
displayVideoSelectTimeout: null,
displayVideo: false,
video: null,
videoPlaying: false,
keyboard: null,
backgroundVideoPos: 0,
backgroundVideoUrl: null,
backgroundVideoKeys: null,
selectionVideos: [],
stackListData: null,
showVideoSelect: function() {
this.set('displayVideoSelect', true);
this.send('resetTimeout');
},
hideVideoSelect: function() {
this.set('displayVideoSelect', false);
clearTimeout(this.get('displayVideoSelectTimeout'));
},
pauseVideo: function() {
this.set('videoPlaying', !this.get('videoPlaying'));
this.set('displayVideoSelect', !this.get('videoPlaying'));
this.set('focus', this.get('videoPlaying'));
},
select: function() {
this.set('videoPlaying', false);
this.set('focus', false);
this.showVideoSelect();
this.send('resetTimeout');
},
cancel: function() {
this.pauseVideo();
this.send('resetTimeout');
},
goNext: function() {
this.pauseVideo();
this.send('resetTimeout');
},
goPrevious: function() {
this.pauseVideo();
this.send('resetTimeout');
},
updateFocus: function(param) {
if (param) {
this.$().attr('tabindex', 2);
this.$().focus();
}//if
else {
this.$().attr('tabindex', -2);
this.$().blur();
}//else
},
init() {
...
},
click() {
this.set('focus', false);
this.showVideoSelect();
},
actions: {
videoSelected(sender, videoData) {
...
},
videoEnded() {
this.set('focus', false);
this.showVideoSelect();
this.set('displayVideo', false);
},
cycleBackground() {
...
},
cancelPressed() {
this.cancel();
},
resetTimeout() {
let component = this;
clearTimeout(this.get('displayVideoSelectTimeout'));
let timeout = setTimeout(() => {
component.hideVideoSelect();
//This set command was responsible for the lag
component.set('focus', true);
}, this.get('data.config.ui.idle') * 1000);
this.set('displayVideoSelectTimeout', timeout);
}
}
});
Related
I'm trying to have my power value go from 100 to 0 and back from 0 to 100. This will be for a power meter where the user will hit a button to stop it at a random value.
Just need help getting the loop working properly
export default {
data() {
return {
power: 100,
};
},
}
watch:{
power: {
handler(value) {
if (value == 100 || value > 0) {
setTimeout(() => {
this.power--;
}, 100);
} if (value == 0) {
setTimeout(() => {
this.power++;
}, 100);
}
},
immediate: true
},
}
Maybe you could find other solutions also, but this is what comes in my mind:
<template>
<section v-if="!showPart">
<!-- This part is shown when program is cycling looping numbers -->
<div v-if="power1">
{{power}}
</div>
<div v-else>
{{powerReverse}}
</div>
</section>
<div v-else>
<!-- This part is shown when user clicks to select a number -->
{{showValue}}
</div>
<button #click="stopFunc">{{textBtn}}</button>
</template>
<script>
export default {
name: "powerCompo",
data() {
return {
speed: 100, // speed of loop
power: 99, // start point of loop
power1: true, // for change view in increase and decrease states
powerReverse: 0, // for increasing loop after reach to "0"
showValue: "nothing selected", // showing the selected value
decrease: null, // for clearing "timeout" in decrease mode
increase: null, // for clearing "timeout" in increase mode
showPart: false, // for changing view between "loop" or "stop" modes
textBtn: "click for stop" // defines the text of btn
};
},
watch:{
power: {
handler(value) {
this.decrease = setTimeout(() => {
this.power--;
}, this.speed);
if (this.power === 0) {
clearTimeout(this.decrease);
console.log("decrease");
this.power1 = false;
this.powerReverse = 0;
this.powerReverse++;
}
},
immediate: true
},
powerReverse(newValue) {
this.increase = setTimeout(() => {
this.powerReverse++;
}, this.speed);
if (this.powerReverse === 100) {
clearTimeout(this.increase);
console.log("increase");
this.power1 = true;
this.power = 100;
this.power--;
}
}
},
methods: {
stopFunc: function ($event) {
/* This function is called each time the user clicks on button. Then if the text of button is "click for cycling again", it calls "resetFunc()" method and if not, it stops looping and shows the selected value. */
if ($event.target.innerText === "click for cycling again") {
this.resetFunc();
} else {
if (this.power1) {
this.showValue = this.power;
if (this.showValue === 100) {
this.showValue = 99;
this.power = 99;
}
} else {
this.showValue = this.powerReverse;
}
this.showPart = true;
clearTimeout(this.increase);
clearTimeout(this.decrease);
this.textBtn = "click for cycling again"
}
},
resetFunc: function () {
clearTimeout(this.increase);
clearTimeout(this.decrease);
this.textBtn = "click for stop"
this.showPart = false;
this.power= 100;
this.power1= true;
}
}
}
</script>
With the above component you can loop between [1, 99] inclusive.
I am trying to setup a button that changes a data value in Vue but also have it set using localStorage initally. This way I can have it keep the previous state it was in before a page refresh. Below is the code I'm using and I'm able to get it to work but know that it would be preferable to use the computed section but haven't been able to get that to work properly.
Would anyone know what is going wrong?
My button is triggered using the testing method and the variable in question is isGrid.
export default {
data() {
return {
option: 'default',
}
},
components: {
FileUploader,
},
mixins: [
visibilitiesMixin,
settingsMixin
],
props: {
vehicleId: {
type: Number,
required: true,
default: null,
}
},
computed: {
...mapState([
'isLoading',
'images',
'fallbackImageChecks',
'selectedImages'
]),
isGrid: {
get() {
return localStorage.getItem('isGrid');
},
},
imagesVModel: {
get() {
return this.images;
},
set(images) {
this.setImages(images);
}
},
selectedImagesVModel: {
get() {
return this.selectedImages;
},
set(images) {
this.setSelectedImages(images);
}
},
removeBgEnabled() {
return this.setting('nexus_integration_removebg_enabled') === 'enabled';
},
},
mounted() {
this.loadImages(this.vehicleId);
},
methods: {
testing() {
if (this.isGrid === 'false' || this.isGrid === false) {
localStorage.setItem('isGrid', true);
this.isGrid = true;
console.log(this.isGrid);
console.log(localStorage.getItem('isGrid'));
} else {
localStorage.setItem('isGrid', false);
this.isGrid = false;
console.log('b');
console.log(this.isGrid);
console.log(localStorage.getItem('isGrid'));
}
},
}
I suggest you use vuex with vuex-persistedstate.
https://www.npmjs.com/package/vuex-persistedstate
I am creating undo/redo functionality in VueJS. I watch the settings and add a new element to an array of changes when the settings change. I also have a method for undo when the undo button is clicked.
However, when the button is clicked and the last setting is reverted, the settings are changed and the watch is fired again.
How can I prevent a new element being added to the array of changes if the settings changed but it was because the Undo button was clicked?
(function () {
var Admin = {};
Admin.init = function () {
};
var appData = {
settings: {
has_border: true,
leave_reviews: true,
has_questions: true
},
mutations: [],
mutationIndex: null,
undoDisabled: true,
redoDisabled: true
};
var app = new Vue({
el: '#app',
data: appData,
methods: {
undo: function() {
if (this.mutations[this.mutationIndex - 1]) {
let settings = JSON.parse(this.mutations[this.mutationIndex - 1]);
this.settings = settings;
this.mutationIndex = this.mutations.length - 1;
console.log (settings);
}
},
redo: function() {
}
},
computed: {
border_class: {
get: function () {
return this.settings.has_border ? ' rp-pwb' : ''
}
},
undo_class: {
get: function () {
return this.undoDisabled ? ' disabled' : ''
}
},
redo_class: {
get: function () {
return this.redoDisabled ? ' disabled' : ''
}
}
},
watch: {
undoDisabled: function () {
return this.mutations.length;
},
redoDisabled: function () {
return this.mutations.length;
},
settings: {
handler: function () {
let mutation = JSON.stringify(this.settings),
prevMutation = JSON.stringify(this.mutations[this.mutations.length-1]);
if (mutation !== prevMutation) {
this.mutations.push(mutation);
this.mutationIndex = this.mutations.length - 1;
this.undoDisabled = false;
}
},
deep: true
}
}
});
Admin.init();
})();
Since you make the changes with a button click, you can create a method to achieve your goal instead of using watchers.
methods: {
settings() {
// call this method from undo and redo methods if the conditions are met.
// move the watcher code here.
}
}
BTW,
If you don't use setter in computed properties, you don't need getters, so that is enough:
border_class() {
return this.settings.has_border ? ' rp-pwb' : ''
},
These watchers codes look belong to computed:
undoDisabled() {
return this.mutations.length;
},
redoDisabled() {
return this.mutations.length;
},
I have the following code in my visual studio and it works perfectly well au a UWP app on my desktop win10, albeit it does not work on my windows phone as a UWP app. I also tried running my simple webapp as from a webserver and loading it in the Edge and it works perfectly.
What should be the problem?
My code looks like this. I omitted some parts:
var model = {
db: {},
goalsobj: {},
goals: [],
init: function() {
var openReq = window.indexedDB.open("GoalsDB");
openReq.onupgradeneeded = function (event) {
model.db = event.target.result;
var objectStore = model.db.createObjectStore("Goals", { keyPath: "id" });
objectStore.createIndex("id","id", {unique:true});
};
openReq.onsuccess = function (event) {
model.db = event.target.result
model.db.transaction("Goals", "readonly").objectStore("Goals").count().onsuccess = function (event) {
if (event.target.result == 0) {
console.log("indexeddb empty");
var goalstemplate = {
id: "idee",
goals: [{ "name": "Task1" }, { "name": "Task2" }, { "name": "Task3" }]
}
var addReq = model.db.transaction("Goals", "readwrite").objectStore("Goals").add(goalstemplate);
} else {
model.db.transaction("Goals", "readonly").objectStore("Goals").get("idee").onsuccess = function (e) {
model.goalsobj = e.target.result;
//console.log(e.target.result);
model.goals = model.goalsobj.goals;
goalfunc.makeList(); //model should not talk to view, but this case it is amust, because if I remove this, it does not render at boot.
}
}
}
openReq.onerror = function (event) {
console.log("Operation failed");
}
}
},
add: function(goalname) {
model.goals.push(
{
"name": goalname
});
model.savedb();
},
move: function (id,updown) {
if (updown == "up") {
model.goals.splice((id-1), 0, model.goals.splice(id, 1)[0]);
};
if (updown == "down") {
model.goals.splice((id+1), 0, model.goals.splice(id, 1)[0]);
};
},
savedb: function(){
//console.log(goals);
var update = model.db.transaction("Goals", "readwrite").objectStore("Goals").put(model.goalsobj);
update.onerror = function (event) {
console.log(event);
};
},
};
Now When I rund this cond on my device it sais:
Unhandled exception at line 28, column 25 in ms-appx-web://1318f74a-397e-4958-aa6b-c8d11b7c5dce/js/main.js
0x800a138f - JavaScript runtime error: Unable to get property 'goals' of undefined or null reference
I have tested your code in my device (Device: Microsoft RM-1118 OSVersion:WindowsMobile 14393). It is working fine. As you can see I placed a button on the html page. The action of button click will execute model.init(), and then I set a break-point at model.goals = model.goalsobj.goals;. When click button the second time and model.goals will be set right value.
So I think the issue may happen in your target device or your GoalsDB was destroyed. Because the cause of Unable to get property 'goals' of undefined or null reference is that model.goalsobj was not set right value. Please check whether those operations have changed your database structure, such as moving operation. You can show more detail about your target device, and I will help you.
(function () {
document.getElementById("createDatabase").addEventListener("click", createDB, false);
function createDB() {
model.init();
}
})();
var model = {
db: {},
goalsobj: {},
goals: [],
init: function () {
var openReq = window.indexedDB.open("GoalsDB");
openReq.onupgradeneeded = function (event) {
model.db = event.target.result;
var objectStore = model.db.createObjectStore("Goals", { keyPath: "id" });
objectStore.createIndex("id", "id", { unique: true });
};
openReq.onsuccess = function (event) {
model.db = event.target.result
model.db.transaction("Goals", "readonly").objectStore("Goals").count().onsuccess = function (event) {
if (event.target.result == 0) {
console.log("indexeddb empty");
var goalstemplate = {
id: "idee",
goals: [{ "name": "Task1" }, { "name": "Task2" }, { "name": "Task3" }]
}
model.db.transaction("Goals", "readwrite").objectStore("Goals").add(goalstemplate);
} else {
model.db.transaction("Goals", "readonly").objectStore("Goals").get("idee").onsuccess = function (e) {
model.goalsobj = e.target.result;
//console.log(e.target.result);
if (model.goalsobj.goals != undefined) {
model.goals = model.goalsobj.goals;
} else {
console.log(e.target.result);
}
//goalfunc.makeList(); //model should not talk to view, but this case it is amust, because if I remove this, it does not render at
}
}
}
openReq.onerror = function (event) {
console.log("Operation failed");
}
}
},
add: function (goalname) {
model.goals.push(
{
"name": goalname
});
model.savedb();
},
move: function (id, updown) {
if (updown == "up") {
model.goals.splice((id - 1), 0, model.goals.splice(id, 1)[0]);
};
if (updown == "down") {
model.goals.splice((id + 1), 0, model.goals.splice(id, 1)[0]);
};
},
savedb: function () {
//console.log(goals);
var update = model.db.transaction("Goals", "readwrite").objectStore("Goals").put(model.goalsobj);
update.onerror = function (event) {
console.log(event);
};
}
};
I have this code, I want to go thru all the links available at the bottom of the page. After clicking them I want to make sure the URL opened is the correct one.
I think the the recursive calls are done too early. Another issue is how can I do to tell that link belongs to certain URL.
function links(browser, total_links) {
if (total_links <= 0) {
browser.end();
return;
}
console.log("Number of links: " + total_links);
console.log('Flag1');
browser
.waitForElementVisible('.bottom .socal>span:nth-child(' + total_links + ')', 1000, function () {
console.log('Flag2');
browser.execute('scrollIntoView(alignToBottom)')
.moveToElement('.bottom .socal>span:nth-child(' + total_links + ')', 3, 3)
.pause(3000)
.click('.bottom .socal>span:nth-child(' + total_links + ') a', function () {
console.log('Flag3');
browser.keys(['\uE006'])
// .assert.urlContains('facebook')
//.assert.urlEquals('https://www.facebook.com/unitel.ao/?fref=ts')
.window_handles(function (result) {
console.log('Flag4');
browser.assert.equal(result.value.length, 2, 'There should be two windows open.');
var handle_1 = result.value[0];
var handle_2 = result.value[1];
browser.switchWindow(handle_2, function () {
browser.closeWindow()
.switchWindow(handle_1, function () {
total_links = total_links - 1;
links(browser, total_links);
});
});
});
console.log('Flag5');
});
console.log('Flag6');
});
}
module.exports = {
'Social links': function (browser) {
var total_links;
browser
.url('http://m.unitel.ao/fit/')
.execute(function () {
return document.querySelectorAll("ul.navbar-nav>li").length;
},
function (tags) {
total_links = tags.value;
links(browser, total_links);
});
// .end();
}
};
Humh, it seems like you were stuck with this days ago.I recommend page-object,it will help you stay away hardcode and easier to change css in the future.
A home page object(home.js) may be like this :
module.exports = {
url: function() {
return 'http://m.unitel.ao/fit/';
},
commands: [{
getUrl: function(n) {
if (n === 3) {
return 'youtube.com/user/tvUNITEL';
}
if (n === 1) {
return 'facebook.com/unitel.ao/?fref=ts';
}
if (n === 2) {
return 'instagram.com/unitelangola/';
}
if (n === 4) {
return 'plus.google.com/110849312028181626033/posts';
}
}
}],
elements: {
facebook: {
selector: '.bottom .socal>span:nth-child(1)',
},
instagram: {
selector: '.bottom .socal>span:nth-child(2)'
},
youtube: {
selector: '.bottom .socal>span:nth-child(3)'
},
googleplus: {
selector: '.bottom .socal>span:nth-child(4)'
}
}
};
And in your test should be like :
module.exports = {
'Social links': function(browser) {
const homePage = browser.page.home();
var j = 0;
for (var i in homePage.elements) {
homePage
.navigate()
.waitForElementVisible(homePage.elements[i].selector, 5000, false,
function() {
browser.pause(3000);
})
.click(homePage.elements[i].selector, function() {
browser
.pause(2000)
.window_handles(function(result) {
url = homePage.getUrl(j + 1);
var home = result.value[0];
var handle = result.value[1];
browser
.switchWindow(handle)
.verify.urlContains(url)
.closeWindow()
.switchWindow(home);
j += 1;
});
})
}
}
};
PS:In case you dont know how to create a page-object, here is the doc http://nightwatchjs.org/guide#using-page-objects.
In config file
Nightwatch.js:
"src_folders" : ["tests"],
"output_folder" : "reports",
"custom_commands_path" : "",
"custom_assertions_path" : "",
"page_objects_path" : "./lib/pages", /* you need to add the path,e.g: './lib/pages', */
"globals_path" : "",