I trying to create a rotating banner for cycle a few images.
const images = [url1, url2 url3]
const rotate = (url) => {
document.getElementById('banner').style.background = url
rotate(next)
}
Url's are correct full paths only shortened here in question. I'm striggl ing to solve how to get next in code above. This is some code I write from looking at examples. Im not a js programmer only recently started. Thank you for helping.
Here's one approach:
const rotate = (element, delay, urls) => {
let current = 0;
(function next() {
element.style.background = url[current]; // set image
current = (current + 1) % urls.length; // update for next pass
setTimeout(next, delay); // recycle
})(); // start immediately
};
with usage:
rotate(document.getElementById('banner'), 5000, [url1, url2, url3]);
The modulo arithmetic resets current to zero after the last image has been shown.
Related
hello i have a sketch which basically pulls from a giant database of over 8,000 images to select an image. sometimes, an image will fail to load and its not feasible for me to go through all 8000 images and find which ones are causing my sketch to freeze on "Loading..."
i just want a way to reset the sketch if the image selected fails to load and just select the next image or a random image or placeholder image in case one fails to load, instead of it just hanging on a "Loading..." screen.
seems like a really simple thing but i can't find any simple solution. i tried making a script to check if "p5_loading" div exists after a Timeout period, but then i realized since the sketch wont load if its broken then my Timeout will never run out to be able to check if it is broken.
thanks.
The bad news is that preload() and loadImage() have some serious shortcomings in this regard. The way preload() works is that when you have it declared, p5.js wraps all of the loadXXX() functions with a function that increments a counter before actually calling the underlying loadXXX() function. Each loadXXX() function then decrements the preload counter, but only if the resource is loaded successfully. As a result, unless one resorts to accessing p5.js internals, there is no way to re-try or recover from an error during preload.
Additionally loadImage() does not have good error handling logic. The issue is that loadImage() first performs an fetch() request for the resource, and if that does not raise an error it creates a Image object and uses its src property to actually load the image data. The problem with that is that it doesn't check the response status code and the Image object does not generate detailed error information. I would go so far as to call this a defect in p5.js worth of filing a GitHub issue. As a consequence, any robust loader that uses loadImage() has to treat all load failures equally (as opposed to an intelligent solution that would treat 5xx errors and timeouts as retry-able, but treat 4xx errors as not worth retrying).
So, given the bad news, what could we do? Roll our own loading screen and use the somewhat suboptimal error handling from loadImage():
const availableImages = [1, 2, 3, 4, 5];
const loading = {};
const loaded = [];
const size = 50;
const maxRetries = 3;
let isLoading = true;
function robustLoad(success, failure) {
// select a random image
let ix = floor(random(0, availableImages.length));
// remove that image from the list of available images
let id = availableImages.splice(ix, 1)[0];
console.log(`attempting to load image id ${id}`);
function tryLoadImage(retryCount) {
loading[id] = loadImage(
`https://robustp5jspreload.kumupaul.repl.co/get-image?id=${id}`,
() => {
// success
loaded.push(loading[id]);
delete loading[id];
success();
},
err => {
console.warn(`error loading image ${id}`);
console.log(err);
if (retryCount < maxRetries) {
console.log(`retrying image ${id}`);
tryLoadImage(retryCount + 1);
} else {
// throw in the towel on this id
delete loading[id];
if (availableImages.length > 0) {
robustLoad(success, failure);
} else {
failure();
}
}
}
);
}
tryLoadImage(0);
}
function setup() {
createCanvas(windowWidth, windowHeight);
background(200);
textSize(48);
textAlign(LEFT, TOP);
text('Loading...', 10, 10);
// attempt to load two random images
let status = [false, false];
for (let n = 0; n < 2; n++) {
let currentN = n;
robustLoad(
() => {
status[currentN] = true;
if (status.every(v => v)) {
// We're done loading.
isLoading = false;
drawBg();
loop();
}
},
() => {
console.warn(`unable to load image ${currentN}`);
}
);
}
}
function drawBg() {
let i = 0;
let j = 0;
for (let y = 0; y < height; y += size) {
for (let x = 0; x < width; x += size) {
image(loaded[i++ % loaded.length], x, y, size, size);
}
i = ++j;
}
}
function draw() {
if (!isLoading) {
circle(mouseX, mouseY, 30);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
Note: This example is not meant to be something you would just use as is. It is just an example of one possible implementation.
The source I'm loading image from is a proof of concept I made on Replit which returns 502 66% of the time. As a result it's theoretically that it will completely fail some percentage of the time.
Im extending the VideoJS default Playlist plugin. Everything is going well tell now, My Issue I need to retrieve each video item duration. As per the API I tried the following:
player.playlist().forEach((item,index) => {
if (player.readyState() < 1) {
// do not have metadata tell this moment.
// waiting
player.one("loadedmetadata", onLoadedMetadata);
}
else {
// metadata already loaded
onLoadedMetadata();
}
function onLoadedMetadata() {
console.log(player.duration()); //<----NEED HELP HERE PLEASE
}
});
The result I got is the first item duration repeated 5 times (count of playlist item) and its not yet loaded in the player window.
Would you please help with fix to show each Playlist video item duration separately?
All the related issues in stackoverflow talking about the player screen itself (I hope I did not miss correct question here) But Im looking for each PlayList item duration.
Thank you.
I'm using the following:
Video.js 7.9.5
video-js-Playlist 4.2
video-js-playlist-ui 3.8.0
I fix my problem with new helper function and 1 extra step in video-js-playlist-ui 3.8.0
The fix as the following:
Step one: Helper function to get the item duration from video-js-playlist-ui plugin:
const itemDuration = function (item) {
const settings = {
itemVideoEl: document.createElement("video"),
itemVideoSource: document.createElement("source"),
itemVideoSrc: item.sources[0].src,
itemType: item.sources[0].type,
};
const { itemVideoEl, itemVideoSource, itemVideoSrc, itemType } = settings;
itemVideoSource.src = itemVideoSrc;
itemVideoSource.type = itemType;
itemVideoEl.appendChild(itemVideoSource);
const getDuration = [];
itemVideoEl.addEventListener("loadedmetadata", (event) => {
const duration = itemVideoEl.duration;
getDuration.push(duration);
});
item.duration = getDuration;
return item;
};
Step two: Add timeout to creating items inside video-js-playlist-ui plugin:
This will guarantee to show the video time in the HTML DOM.
class PlaylistMenuItem extends Component { //default class
createEl() { //default function
const item = itemDuration(this.options_.item); //<---REPLACED WITH THE NEW HELPER FUNCTION
const li = document.createElement("li");//default value
const showDescription = this.options_.showDescription;//default value
setTimeout(() => {
//The rest of the default function createEl() comes here.
},1000);
}
}
NOTE:
My fix is working for HTML5 Video/Audio only, I know more techs will need extra steps, so this is only a hint for anyone may stuck in the same situation. Hope this answer will help other people as I always get help from here.
Thank you.
I am getting started with react-native development. I am using react-native-video to add a video in my application.
I wanted to seek back and forth in a video in a for-loop and hold it for a given timeout using React-Native. I am using react-native-video as the video component. The following is my code.
for (let i = 0; i < count; i++) {
var v1 = data.ModelList[i].Value;
var duration = data.ModelList[i].Duration;
new Promise((resolve) => {
setTimeout(() => resolve(this.player.seek((v1*10+15),0)), duration);
});
}
Expectation: In each iteration, I want to seek the video and wait for certain milliseconds and move to the next iteration of the for-loop.
Can someone help me with this
Finally managed to create a recursive fucntion which will do the job.
videoSeekAnim(data, count) {
this.player.seek(newC, 100);
if (count < ItemCount) {
setTimeout(function () {
this.videoSeekAnim(data, count + 1);
}.bind(this), 20);
}
}
}
This works. But it is not smooth engouh. Hope this might be helpful for someone.
I'm new to javascript, and I've been stuck on this simple problem for days. I have a series of thousands of images (collectively a "dataset") that when viewed rapidly one after the other gives the appearance of video. I want to loop through all of my images.
I've created a function like so:
function updateImage(dataset, record_id) {
image_url = '/image?dataset='+dataset+'&record-id='+record_id;
if ($('#mpeg-image').length > 0) {
$('#mpeg-image')[0].src = image_url;
} else {
$("#image-thumbnail").html('<img id="mpeg-image", src="'+image_url+'"> </img>');
}
}
Calling the function once, e.g., updateImage('dataset_28_18-08-11',444); results in the image content updating in the browser. However, I want to show the images in a loop, so I created a function like this:
function playVideo() {
for (i = 0; i < 1800; i++) {
updateImage(dataset,i);
}
}
This function uses the same updateImage() function that worked above, but when I run playVideo(); Chrome doesn't update the image until the very end. The src tag changes, but the actual content does not. How can I alter my loop so that each image is loaded and shown in sequence? I don't want to use something like "Sprite" that merges all images into one, since that's too data intensive on my backend. I just want to load and show my images one after the other.
Browsers won't re-render page elements until there's a break in the execution of your code. Your entire image loop will run before the browser ever gets a chance to re-paint the images.
It's not because the loop "runs too fast" or because the images haven't loaded (though that can definitely pose problems). To see an example of redrawing issue, try changing your animation into an infinite loop, where it continually plays. The page will freeze ("pinwheel") forever and the page elements will never be updated, even long after all of your images have loaded. You need to give the browser an opportunity to redraw the page whenever you've made changes to page elements.
In web animation, this is often done via window.requestAnimationFrame() (see MDN docs). The method takes a callback function which is executed after the browser has redrawn the page. The browser will execute your callback once it's ready for page changes.
A simplistic example of this with you code would go something like this.
var imageNo = 0;
function step() {
updateImage(dataset, imageNo);
imageNo++;
if (imageNo < 1800) {
window.requestAnimationFrame(step);
}
}
window.requestAnimationFrame(step);
You can achieve a similar effect using setTimeout() or setInterval(), but those methods aren't optimized for animation.
The reason why you only see the last image is because of two things.
1. as #andriusain said, it takes the browser some indeterminate amount of time to download each image.
2. The loop runs so fast that the last iteration is what takes affect.
With that said, it would be best to either sprite the image (which it sounds like you can't do), or you can preload the images so that the browser caches them, then set the urls. You can do something like this.
// using es6
// I'd recommend adding <img src="mpeg-image" /> to your html page
// just to simplify the code. Also I recommend saving the image
// element in a local variable instead of using jQuery in each
// iteration loop which will definitely give a slower performance.
const mpegImgElement = $('#mpeg-image')[0];
const FPS = 30 / 1000; // 30 Frames Per Second
function playVideo() {
preLoadImages()
.then(urls => {
playUrls(urls);
})
.catch(error => {
console.log('error loading images', error);
})
}
function playUrls(urls) {
const index = 0;
const intervalId = setInterval(() => {
// set the url
mpegImgElement.src = urls[index];
// all done. stop the interval
if (++index === urls.length) {
clearInterval(intervalId);
}
}, FPS);
}
function preLoadImages() {
const promises = [];
for (i = 0; i < 1800; i++) {
const imageUrl = '/image?dataset='+dataset+'&record-id='+record_id;
const promise = loadImage(imageUrl);
promises.push(promise);
}
return Promise.all(promises);
}
function loadImage(url) {
return new Promise((resolve, reject) => {
const image = new Image();
image.addEventListener('load', () => {
resolve(url);
});
image.addEventListener('error', error => {
reject(error);
});
image.src = url;
});
}
This happens because the browser is still loading the images... I think your best bet is to programmatically load them first and once they are loaded then play the video... to do that maybe something like this will help:
var loadImages = function(images, callback) {
var loadTotal = 0;
for (var i = 0; i < images.length; i++) {
var image = new Image();
image.addEventListener('load', function() {
loadTotal++;
if (loadTotal === images.length) {
callback();
}
});
image.src = images[i];
}
}
var playVideo = function() {
// do stuff...
}
var images = ['image1.jpg', 'image2.jpg'];
loadImages(images, playVideo);
This idea is terrible.
JS is executed in single thread, while calling data from src is asynchronous method. Calling one by one and wait then definitely block the thread.
In this case you need promise to prepare all images first.
I make a workable example using google logo.
paths = ["https://www.google.com.hk/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png",
"https://www.google.com.hk/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png",
"https://www.google.com.hk/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png"]
var imgCount = paths.length;
function loadImage(url) {
return new Promise((resolve, reject) => {
let img = new Image();
img.addEventListener('load', e => resolve(img));
img.src = url;
});
}
promises = []
for (var i = 0; i < paths.length; i++){
path = paths[i];
promises.push(loadImage(path));
}
Promise.all(promises).then(imgs => {
imgs.forEach(e => document.getElementById('image-holder').appendChild(e));});
//What I done here is display then all, but you can make it like, replace one by one, or declare your own display function.
<div id="image-holder"></div>
I'm using angularjs as front-end framework. I get a data set via a rest get call.
That contains coordinates in an array. What needs to be done is that array with coordinates should be iterated and should be shown in a map.
Once the user hits play in the map those coordinates should be displayed one ofter another having at least 1 second of interval.
And when user hits play button, the button it self converts to a pause button it should do what the name intend to do. pause the process. I couldn't achive this kind of behavior using angularjs. Following is the closest that I could come.
var getReplayData = function () {
return $http.get('http://localhost:4000/replay/asiri/vin/' + from + '/' + to);
};
$scope.play = function () {
from = rangeProperties.getFrom() * 1000;
to = rangeProperties.getTo() * 1000;
getReplayData().success(function (data) {
console.log(data);
var waitingTime = 0;
var gap = 0;
for (var i = 0; i < data.length; i++) {
(function (i) {
var element = data[i];
var coordinates = new Object();
coordinates.latitude = element.LATITUDE;
coordinates.longitude = element.LONGITUDE;
setTimeout(function () {
broadcastData(coordinates);
}, waitingTime);
if (i + 1 < data.length) {
gap = data[i + 1].TIMESTAMP - element.TIMESTAMP;
console.log(gap);
} else {
gap = 0;
}
waitingTime = waitingTime + gap;
})(i);
}
});
$scope.play refers to the play action of the button. I can't figure out how to pause this process. Seems like I might have to keep the references to all timeouts and cancel them. Any idea how to implement this kinda of scenario? I don't exactly need a code segment just an idea how to approach to solve this kinda of problem would be really nice.
Seems like I might have to keep the references to all timeouts and cancel them.
That would be a good first step.
I don't exactly need a code segment just an idea how to approach to solve this kinda of problem would be really nice
I would do the following:
cache the response data as a variable, if possible.
separate the play and pause functionality, tying them into a cancelable callback that you get from using the $interval service.
during playback, treat your coordinates data as a FIFO (queue), dequeuing the item which gets passed to the $interval's promise
during pause, you simply cancel the $interval promise
playing again doesn't require any fancy work on the queue as you'll resume the same logic on the first item in the queue when you resume.