The problem was that while the overall progress bar worked correctly, the individual file progress bar completely stopped working when the first file finished uploading. I checked the numbers, and they were fine, so the problem had to lie with the component.
The explanation is that the timer used to update the progress bar is stopped once the bytesLoaded property equals bytesTotal. Adobe will most surely call this a feature, as it saves resources for most use cases, when the progress bar is used to track a single operation at a time. However, in my case it turned out to be a bug, since I needed to track multiple operations. Had I been aware of it, I would probably have engineered my code to use the event or manual mode. But Adobe have either not documented this, or have hidden it very cleverly.
The solution in my case was either to prevent the timer from stopping or to restart it after the progress reached 100%. I did not want to keep it going indefinitely, as that would have cause the kind of resource leak which Adobe tried to prevent. So I looked in the ProgressBar class for instances where the timer was restarted, and it seemed that the best way to force a restart would be to toggle the mode property itself when a file finished uploading:
this.currentFileProgressBar.mode = ProgressBarMode.MANUAL;
this.currentFileProgressBar.mode = ProgressBarMode.POLLED;
There would have been other alternatives, of course, but this worked best in my project.
One of the alternatives is to use the BetterProgressBar component in the CleverPlatypus framework, which simply adds a value setter to the progress bar, as you would expect it to have anyway.