Hi everyone !
I am trying to develop an app that computes and displays animations of satellite images , based upon acquisition parameters specified by the user.
I have been able to generate these animations and display them with a kind of “hacky” procedure : by using matplotlib.Animation.to_html5_video()
and putting the resulting html in a st.markdown(html_video, unsafe_allow_html = True)
. I know I could have used streamlit.components.v1
according to this topic, but then I would have not been able to adapt the size and layout of the video generated.
Either way, I have several questions :
- I have the user submit a time (with a slider in a form) at which the animations should be starting. When I submit a new timestamp, the content updates correctly (the titles and static images) except for the animations, which remain the same (see screencast below). I feel like they are cached or something. Can I somehow force the app to actually recalculate everything again ?
- I am struggling to make the videos play all at once. I have tried to modified the HTML
autoplay
attribute of the generated videos and tried to add a custom javascript to the app as well, with no success.
- Bonus question: Is it possible to zoom dynamically on an image in streamlit ?
Link to the screencast on google drive
Thanks for your help !
Specs:
- Streamlit 0.82
- Python 3.9.1, using conda
- Ubuntu 20.04
- Firefox 88.0.1 (64 bits)
Update: By using streamlit.components.v1
, the animation updates correctly but it messes up my layout like so:
So I guess a solution would be to find a way to modify the video size to make it fit inside the iframe that is created by components ?
I am closing this topic with these conclusions :
- Using
streamlit.components.v1
ensures that the animations get updated but I couldn’t find a way to adjust dynamically the size of the figures.
- Here below you can find the javascript that I use to detect the created iframes for each animation, then fetch the video objects, stopping them and re-playing them from scratch all at once. This works well when the app is loaded once, but the script is not executed again upon reload.
- Still not feasable.
<script>
function detect(){
console.log('Launching video script')
// Get all videos.
var iframes = parent.document.querySelectorAll("iframe")
var videos = [];
iframes.forEach(function(frame){
var video_list = frame.contentDocument.querySelectorAll('video');
if (video_list.length != 0){
var video = video_list[0]
console.log('Pausing and reloading videos')
video.autoplay = false
video.pause()
video.load()
// Add video to the detected videos list
videos.push(video)
}
});
return {iframes: iframes, videos: videos};
}
// Get the videos from the page
var res = detect()
var iframes = res.iframes
var videos = res.videos
iframes.forEach(function(iframe){
// Add an event listener to ensure that the detection gets reexecuted even on page reload
iframe.addEventListener('load', function(){
console.log('load')
console.log(iframe)
});
iframe.addEventListener('error', function(){
console.log('error')
});
});
// Create a promise to wait all videos to be loaded at the same time.
// When all of the videos are ready, call resolve().
var promise = new Promise(function(resolve) {
var loaded = 0;
console.log('Waiting for all videos to be loaded')
videos.forEach(function(v) {
v.addEventListener('loadedmetadata', function() {
loaded++;
if (loaded === videos.length) {
resolve();
}
});
});
});
// Play all videos one by one only when all videos are ready to be played.
promise.then(function() {
videos.forEach(function(v) {
v.play();
console.log('Playing video')
});
});
async function test(iframes){
while (true){
await new Promise(r => setTimeout(r, 5000));
console.log('Script is executing')
console.log(iframes)
}
}
test(iframes);
</script>
One very last thing : by playing with both the figure size and the dpi when plotting my animations, I can now adjust more or less dynamically the displayed animations. But of course, it supposes a tradeoff in video quality and resolution…
1 Like