Home About Me

The Bilibili Bug That Only Shows Up If You Click at Exactly the Wrong Millisecond

I was just watching a video. The uploader was wrapping up, and down in the recommendations something worth clicking caught my eye, so I clicked it.

Simple enough. But that click landed in a very narrow window: the video had just finished, and the client's code was already moving to switch to the next video in the collection. My tap interrupted that handoff, and the player lost track of its own state.

Client version: 8.83.0 (release-b23203277), which is the latest build in the market at the time.

Whether or not that made sense, here's what happened:

I only hit record because the bug had already appeared — nobody expects a random click to turn into a test case. The result: a black screen with audio still playing, no picture at all, and the progress bar wouldn't even come up.

So I retraced what I'd done. Roughly this:

While a video in a collection has finished and is about to switch to the next one, tapping a recommended video makes the newly loaded video play audio with no picture. You can't even pull up the progress bar.

That sounds easy to reproduce. Except it isn't. The timing has to line up three ways at once: the video has to hit its end, the playback control code has to be on the verge of switching, and the click has to land right then to interrupt the state change. That's a lot of buffs stacked on top of each other. (Of all the places to spend my luck.)

Being the thorough type, I went back to try again — and found a slightly different problem:

This time I got it stuck in one attempt, but not identically. The progress bar appeared (a zero-second video of pure air), and the middle buffer spinner showed up too. So it wasn't a completely black screen — there was progress, there was loading. Meaning that in this particular timing window, the player hadn't lost its state entirely. A slightly milder flavor of the same freeze. The detail here: the video I jumped to returned directly.


Someone will reasonably ask: your device is rooted and running LSPosed — how do you know those didn't affect the environment or change the behavior?

Fair. So I tested on a second phone. This Huawei has no root and can't be rooted. A completely trustworthy environment, this time it should be a fair test:

Well then. This time I even got a seven-minute zero-second video, which suggests the amount of elapsed time affects how much state gets lost. I also had to try three times to trigger it once — that's how narrow the window is. The detail here: after jumping, I paused the video and then pressed back.

If you still doubt it, go test it yourself — just be warned it's extremely timing-sensitive. Those are basically the methods; dig deeper and you'll probably find other variations.

The two devices were a OnePlus PKX110 (rooted) and a Huawei CDY-AN00 (bootloader never unlocked). The catch: the bug-report mini-program only accepts one device's information. I can't exactly fuse them into a "HuaPlus Phone," so I picked one of the two. (Did nobody designing the submission system consider that people might test on more than one device?)

The issue has been submitted. When it gets fixed is anyone's guess.

To sum up: this is almost certainly a race condition during video switching, where the code's assumptions about player state stop matching reality.

Minimal reproduction steps:

  1. Find a collection with plenty of videos.
  2. Pick any video in the middle of the collection.
  3. Drag the progress bar to about ten seconds before the end.
  4. Watch the progress bar; at the instant it fills and you feel the switch coming, tap a recommended video below (important: be fast!).
  5. Observe the playback behavior of the recommended video you landed on.
  6. Press back and observe the playback behavior of the collection video.

(Detail: before pressing back, you can choose whether to pause the video you jumped to — the behavior after returning to the collection video may differ.)

And that's how one coincidence turned into a whole investigation. The underlying problem is simple: competing code paths both trying to switch. With proper boundary checks on Bilibili's side — or dedicated handling for this exact edge case — it should be fixable.