Multimedia

Phonon and the Boring Audio Button

My sound preview worked with the Xine Phonon backend and failed as soon as I switched the machine to GStreamer. Same file, same button, no sound. So much for my five-line multimedia triumph.

The application called play() and immediately checked state(). Xine reached PlayingState quickly on my machine. GStreamer was still in LoadingState, which my code treated as failure and answered with stop(). I had accidentally made backend speed part of the program’s contract.

Phonon state changes are asynchronous. The fix was to let the MediaObject report stateChanged() and handle ErrorState there instead of demanding synchronous playback:

connect(media, SIGNAL(stateChanged(Phonon::State, Phonon::State)),
        this, SLOT(mediaStateChanged(Phonon::State, Phonon::State)));
media->setCurrentSource(fileName);
media->play();

The slot updates the button when playback really starts and displays errorString() if the object enters ErrorState. LoadingState is just progress, not proof that the backend rejected the file.

Switching backends was useful precisely because it broke this. The common API kept Xine and GStreamer details out of the application, but it did not turn asynchronous operations into synchronous ones. One backend had merely been fast enough to hide my bad assumption.

The preview button is boring again. I left both backends in the test pass; otherwise the next timing assumption will wait for a user to find it.

Phonon Is Not a Codec Collection

A test player handled one Ogg file and failed on another machine before lunch. My first reaction was to ask which Phonon codec was missing. That question contains the bug in my mental model.

Phonon is intended as a multimedia API above actual playback engines. The application builds a graph of media objects, audio outputs, and paths. A backend translates that graph to whatever engine is available. Codec support therefore depends on the backend and its installed components, not on Phonon carrying a suitcase of decoders.

The small playback case looks roughly like this:

Phonon::MediaObject *media = new Phonon::MediaObject(this);
Phonon::AudioOutput *output =
    new Phonon::AudioOutput(Phonon::MusicCategory, this);

Phonon::createPath(media, output);
media->setCurrentSource(Phonon::MediaSource(fileName));
media->play();

The category matters. It describes the purpose of the sound so system policy can distinguish music from notifications or communication. It is not merely a friendly enum to satisfy the compiler.

Errors remain asynchronous. Calling play() starts a process; it does not certify that bytes will reach speakers. The application must listen for state changes and present the backend’s error without pretending every failure is a missing file.

I prefer depending on a narrow playback abstraction instead of teaching each KDE application the details of several multimedia engines. That should make device selection and policy consistent. The caveat is the least-common-denominator problem: specialised editors and unusual pipelines may need capabilities the abstraction cannot sensibly expose.

I am also avoiding backend detection in the application. Branching on a backend name would make today’s workaround tomorrow’s required behaviour. If a capability is necessary, the API should expose that capability or the application should report that it cannot perform the operation.

For porting, I am keeping the boundary obvious. The document remembers a URL and playback position; a small controller owns Phonon objects; widgets issue commands and display state. This makes backend surprises testable without turning the main window into an audio engine wearing buttons.

The architecture is still settling, and backend behaviour will not be perfectly identical. Today’s useful certainty is modest: “works through one engine here” is not the same claim as “Phonon supports this format everywhere.” Computers remain attentive readers of fine print.