Blazor list not updating after calling functions on it

Viewed 326

I'm developing a webpage using a video player in blazor web-assembly (.NET 6). This blazor webpage uses a list of Urls which does not update correctly.

General page logic: The page creates an IList Videos of a self-declared class ExcerciseVideo. This IList should be changed after initialization, depending on user-input by two functions.

  1. RepeatExercises: This function is called after a button press and repeats certain videos a specified number of times. It is necessary because several videos must be repeated. I want to do this by using a playlist, not by looping the video.

  2. ModifyUrl: This function is called after a button press and should change only the videos with a true two-sided attribute. The variable currentUrl should be set for the first video to Url1 (when sidePlayNext == "left") and for the second same video to Url2 (when sidePlayNext == "right).

I've simplified the code as much as possible.

 @page "/"
@inject IJSRuntime theJavaScriptEngine;

<!-- Buttons for interaction -->
<button @onclick="StartVideo">Start video</button>
<button @onclick="RepeatExcercises">Repeat Excercises</button>
<button @onclick="ModifyUrl"> Modify Url (of Two Sided Videos)</button>

<!-- Video Element -->
 <div class="align-content-center">
    <video id="videoTagId" autoplay width="1080" height="720"  @onended="NextVideo">
        <source id="videoSourceId" src="@Videos[selected_video_id].CurrentUrl" type="video/mp4"/>
    </video>
</div>

<!-- Developer output -->
@foreach(var v in Videos)
{   
     <li><code>Name: </code>@v.Name</li>
     <li><code>Video Current URl</code>@v.CurrentUrl</li>
     <li><code>Video URl 1</code>@v.Url1</li>
    <li><code>Video URL 2</code>@v.Url2</li>
    <li><code>Two Sided</code>@v.TwoSided</li>
    <li>...</li>
}

@code
{
    public int selected_video_id { get; set; } = 0;
    public IList<ExcerciseVideo>? Videos; // The list we use to store the Videos
    public int Repetitions { get; set; } = 2; // we want each exercise two times
    public string sidePlayNext {get; set; } = "left"; 

    public class ExcerciseVideo
    {
        public string Name { get; set; }
        public string CurrentUrl { get; set; }
        public string Url1 { get; set; } 
        public string Url2{ get; set; } // right hand side of each excercise
        public bool TwoSided { get; set; }
    }

    protected override void OnInitialized()
    {
        Videos = new List<ExcerciseVideo>()
            {
                   new ExcerciseVideo {   
                                Name="Excercise A",
                                CurrentUrl = "https://www.learningcontainer.com/wp-content/uploads/2020/05/sample-mp4-file.mp4",
                                Url1="https://backtrainingapp.blob.core.windows.net/backtrainingvideos/20220220_184806[1].mp4",
                                Url2="https://backtrainingapp.blob.core.windows.net/backtrainingvideos/20220220_184806[1].mp4",
                                TwoSided=false},
                  new ExcerciseVideo {   
                                Name="Excercise B",
                                CurrentUrl = "https://www.learningcontainer.com/wp-content/uploads/2020/05/sample-mp4-file.mp4",
                                Url1="https://backtrainingapp.blob.core.windows.net/backtrainingvideos/20220220_184632[1].mp4",
                                Url2="https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/360/Big_Buck_Bunny_360_10s_1MB.mp4",
                                TwoSided=true}

            };

    }

    public void RepeatExcercises()
    {
        // obtain values of the Video List
        int old_video_count = Videos.Count;
        int my_index = 0; 

        // Create a copy of the Video List
        IList<ExcerciseVideo> originalVideos = new List<ExcerciseVideo>(Videos);
        Videos.Clear(); // Clear the list --> we need a clean list for item insertion (in order to not have old items included) 

        // we loop over the list to repeat Videos
        // and insert them
        for (int l = 0; l < old_video_count; l++)
        {
            for (int repeats = 0; repeats < Repetitions; repeats++)
            {
                Videos.Insert(my_index, originalVideos[l]);
                my_index++; // increment the index
            }
        }
        
        StateHasChanged(); // Make sure that the changes are updated             
    }

    // Modify the Url of twoSided Videos
    public void ModifyUrl()
    {

        for (int m = 0; m < Videos.Count; m++)
        {                    
            // check whether a video is twosided
            if (Videos[m].TwoSided == true)
            {

                // check whether left side should be played
                // set Url1 in this case as value
                if (sidePlayNext == "left")
                {
                    Videos[m].CurrentUrl = Videos[m].Url1;
                    Console.WriteLine(sidePlayNext);
                    Console.WriteLine(Videos[m].CurrentUrl);
                    sidePlayNext = "right";
                }

                // check whether right side should be played
                // set Url2 in this case as value
                else if (sidePlayNext == "right")
                {
                    Videos[m].CurrentUrl = Videos[m].Url2;
                    Console.WriteLine(sidePlayNext);
                    Console.WriteLine(Videos[m].CurrentUrl);
                    sidePlayNext = "left";
                }

            }

            // All other cases in which no two sided videos are present
            // --> Current URl should correspond to Url1
            else if (Videos[m].TwoSided == false)
            {
                Videos[m].CurrentUrl = Videos[m].Url1;
                Console.WriteLine("No Direction");
                Console.WriteLine(Videos[m].CurrentUrl);
            }
        }

        StateHasChanged();          
    }

    protected void StartVideo()
    {
        selected_video_id = 0;
        theJavaScriptEngine.InvokeVoidAsync("loadVideo");
    }

    protected void NextVideo()
    {
        selected_video_id = selected_video_id  + 1;
        theJavaScriptEngine.InvokeVoidAsync("loadVideo");
    }

}

Testing and issues encountered (please also see the blazor fiddle: https://blazorfiddle.com/s/3ltiwlv3 ):

  • Please check for testing the output below the video --> The list when the video loads seems to be populated correctly
  • Then press the button "Repeat Excercises" --> The videos appear to be repeated as intended in the output below the video
  • Then press the button "Modify Url" --> Look at Excercise B. Unfortunately, the currentUrl of the first and second "Excercise B" are equal. The list in the second entry of Excercise B does not update to the value in Url2.

I've tested the functions "RepeatExcercises" and "ModifyUrl" in Console apps and they work well there and update the list "Videos" as intended. Ultimately, I would also like to update the src attribute of the VideoPlayer. I've tried it with blazor bindings, but have also read that resetting sources of videos is difficult when already loaded (especially of a Url which on a specified index of a list...) - how could I do this?

<!-- Video Element -->
 <div class="align-content-center">
    <video id="videoTagId" autoplay width="1080" height="720"  @onended="NextVideo">
        <source id="videoSourceId" src="@Videos[selected_video_id].CurrentUrl" type="video/mp4"/>
    </video>
</div>

I've read up on all similar questions I could find online but am completely stuck now. I would greatly appreciate help.

Best regards & thank you Max

1 Answers

Blazor is just working fine.

Q1: updating url

The problem is with your code. When you press 'Repeat Exercises' you are inserting for twice the ExcerciseVideo Excercise B. Notice that do you have only one instance of Excercise B inserted for twice in the list (same instance object two times in the list).

When you iterate over the list, you found Excercise B for twice:

  A
  A
  B
  B  <--- // Is the same ExcerciseVideo instance as previous

Then, on first B you change url to some value and, on second B (that is the same B as before) you change back the url.

To fix your typo, just change function to:

public void RepeatExcercises()
{
    // obtain values of the Video List
    int old_video_count = Videos.Count;
    int my_index = 0; 

    // Create a copy of the Video List
    IList<ExcerciseVideo> originalVideos = new List<ExcerciseVideo>(Videos);
    Videos.Clear(); // Clear the list 

    // we loop over the list to repeat Videos
    // and insert them
    for (int l = 0; l < old_video_count; l++)
    {
        for (int repeats = 0; repeats < Repetitions; repeats++)
        {

            var new_video =  new ExcerciseVideo {   // <-- Important part
                            Name=originalVideos[l].Name,
                            CurrentUrl = originalVideos[l].CurrentUrl,
                            Url1=originalVideos[l].Url1,
                            Url2=originalVideos[l].Url2,
                            TwoSided=originalVideos[l].TwoSided};

            Videos.Insert(my_index, new_video);
            my_index++; // increment the index
        }
    }
    
    StateHasChanged(); // Make sure that the changes are updated             
}

Q2: update video src

About second part of your question, update src attribute, is full explained at:

C# StateHasChanged() in a razor component does not force tag to load/change the mp4 playing, only using javascript seems to work

Related