MediaIoFactory
Makes a fresh MediaIo for one playback session.
Called once per open, and opens happen more than once: a track switch, a loop, a recovery and a queue wrap all reopen. Handing back the same reader every time means the second open is given the one the first session already closed, and the media simply stops.
A named interface rather than a bare lambda so a door can return one and this contract has somewhere to be written down. Kotlin converts a lambda at the call site, so io = { reader } keeps compiling exactly as it did.