Hook before audio playback change

I would like to check/switch if needed hardware amplifier inputs BEFORE playback starts to make sure they are switched correctly. I understand I could use my own Audio class which implements the hook. But please is it possible to implement this without modifying the existing extensions which use the built-in Audio class of Mopidy? It would not have to be synchronous, just catching some event about_to_start_playback, similar to the existing call about_to_finish.

Or using a modified core.PlaybackController but I would love to keep the mopidy code intact.

Thanks a lot for any hints.

Pavel.

I am studying the source code. Mixer can be configured “mopidy-wide” by any extension with registry “mixer” and used in https://github.com/mopidy/mopidy/blob/70e510e45985cc5d40a9adecf07980d0f96b3a1f/mopidy/commands.py#L295 . Do you think there is any chance the Audio class could be configured for whole mopidy in the same way, would you accept such patch? Or should I just just monkey patch the method RootCommand.start_audio at https://github.com/mopidy/mopidy/blob/70e510e45985cc5d40a9adecf07980d0f96b3a1f/mopidy/commands.py#L372 in my code?

Thanks a lot, Pavel.

Are you saying that the existing track_playback_started or playback_state_changed core events are not useful to you here? Presumably whatever you do to switch inputs is not instantaneous and you can’t block so does it really matter if you do this before or just after?

The appliance I am working on is a combination of a PC and integrated amplifier with multiple inputs. The PC soundcard is hooked to one of the inputs. Before starting any playback from gstreamer I have to make sure that particular input is selected first to avoid any clicks/music cuts.

So actually I have to implement a synchronous action. My idea is to extend the existing Audio.set_uri method to do the check first, and “inject” the modified class to mopidy somehow. I like the way components can inject system-wide mixer facility, I thought the same could work for audio too.

I can always use a forked mopidy but if there was a way to use the latest stock one… you know what I mean.

I very much appreciate your help. Pavel.

I don’t think blocking in set_uri() is a good idea as it can potentially break gapless playback, start_playback() would probably be a safer place to do something like this. The Audio class isn’t really suitable for extending as is, it would probably need to be broken up into an extendable part and a fixed part.

Personally, I think that what you want to do sounds exactly like the sort of thing that should live in a mixer. Maybe we could just extend that API with a enable_output() method that’s called from PlaybackProvider.play(), or with more work, Audio.start_playback().

Thanks a lot for the warning, start_playback looks good.

There is already a workaround in Audio._setup_preferences with the TODO comment in commit https://github.com/mopidy/mopidy/commit/fd9100a5f330db4d98d32abe4d7dbb62330e21f2#diff-41786cf21d61f8d773107bc83320f891R304 . I would prefer being able to insert a piece of code (or extend, or any other way to run custom code before actual playback starts).

However, each backend should have a way to override the default enable_output behaviour. I will have AnalogInputBackend and SPDIFInputBackend, which will not play anything through gstreamer, just reconfigure the input switch (for AnalogInputBackend) or the soundcard controls (for SPDIFInputBackend). These backends will publish their own playlists with their own uri_schemes. When one of their tracks is selected to play, the backend should switch the inputs or reconfigure the soundcard accordingly. That way any MPD/web client can control hardware facilities of the appliance.

My idea was to catch the to-be-played uri in my set_uri(uri) “hacked” method and act as relevant for the actual uri_scheme. I am not sure the Audio.start_playback() or potential mixer.enable_output() would have this key information available. Unless the potential method was overridable in individual backends, that would be perfect solution.

But AFAIU I can provide my own audio class for these nonstandard backends so that should be doable with existing API.

I very much appreciate your opinion.

Pavel.

I tried to modify the audio class for CDBackend to find out the Audio class is initialized system-wide in https://github.com/mopidy/mopidy/blob/develop/mopidy/commands.py#L374 I need to set specific source property (read-speed) in the _on_source_setup handler. I see properties for a specific source are setup e.g. in utils.setup_proxy https://github.com/mopidy/mopidy/blob/develop/mopidy/audio/utils.py#L67 by checking if the corresponding property exists.

I changed the code of the method to my specific

def setup_proxy(element, config):
    if hasattr(element.props, 'proxy') and config.get('hostname'):
        element.set_property('proxy', httpclient.format_proxy(config, auth=False))
        element.set_property('proxy-id', config.get('username'))
        element.set_property('proxy-pw', config.get('password'))

    if hasattr(element.props, 'read-speed'):
        element.set_property('read-speed', 1)

Now the CD plays perfectly silent. I am afraid the only way for me will be forking the code and rebasing my own modified version.

Given your specialist needs I agree that’s the best course of action for you. All Mopidy Backend playback goes through gstreamer and bypassing that as you want to do is out of scope for us.

What you want to do doesn’t sound too dissimilar to how we juggle multiple audio servers in Pi Musicbox. Here we have Mopidy, Shairport-Sync (Apple Airplay) and librespot (Spotify Connect) all wanting to use the sound device and only Mopidy does so via gstreamer. In theory we could try and so something similar to what you want but since each of those applications already has it’s own client interface, it’s easier to keep everything totally separate. It would be slick to have the status or even control of all three applications under one unified interface, I totally agree. But I think if we did that we’d probably do so via a separate overarching manager-type program, rather than trying to integrate it into Mopidy.

Good luck with your system.

Nick, thanks a lot for your important answer. I will go ahead with my project in this direction.

Actually I did not know Pi Musicbox had a separate Spotify web client. I understand the reasons though. For me the MPD API (i.e. all the MPD clients) and the existing web clients such as Iris are still the major attraction to use Mopidy to control other device features.

I thought of providing a “more complete” version of the existing https://github.com/forscher21/mopidy-cd extension with the usability features added (medium detection/recovery, read-speed reduction, etc.), I am afraid that extension will not work without some of the patches/commits I will have to do regarding the existing audio class in my fork of mopidy.

Once more thanks a lot for your help.