Matplolib animation not updating

Hi everyone ! :relaxed:

I am trying to develop an app that computes and displays animations of satellite images :artificial_satellite:, 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 :

  1. 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 ?
  2. 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.
  3. 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 ?

Update:

  1. In the end, I used streamlit.components.v1 with a hard coded figsize that fits my layout. Not the best but it’s ok.
  2. It is definitely possible, I’ve succeeded to play all the videos all at once, though I am struggling to do the same when the app reloads. In fact, the script is running in a streamlit.components.v1 as well and unfortunately, it doesn’t get reloaded with the app (with a slider update or upon hitting R). I am still investigating and I’ll be sure to post down below my script when I achieve this.
  3. It doesn’t seem possible… :sob: :pleading_face:

I am closing this topic with these conclusions :

  1. 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.
  2. 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.
  3. 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