Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gapless part 1 #1288

Merged
merged 34 commits into from Oct 7, 2015
Merged

Gapless part 1 #1288

merged 34 commits into from Oct 7, 2015

Conversation

adamcik
Copy link
Member

@adamcik adamcik commented Sep 16, 2015

This is a major cleanup of core playback internals. Some important things to note:

  • We no longer emit the "silly" state changed events between tracks. This might confuse clients, so we could add playing -> playing type events.
  • play, next, prev are now async. Tests must take care to wait for core futures to finish. Flow is not that these calls, about about to finish set the pending track. Once stream changed comes in we switch. This might also confuse clients.
  • There is a race after about to finish and before stream changed were we can seek the wrong song. Fix postponed to later gapless followup branch.
  • play, next, prev still break streaming, we need to make all changes async by faking end of stream events. Postponed for now.
  • We should review my event spreadsheet and make sure we have things cleaned up. I also suspect we are missing some calls to add history.

While trying to remove traces of stop calls in core to get gapless working I
found we had no way to switch to switch tracks without triggering a play. This
change fixes this by changing the backends playback provider API.

- play() now _only_ starts playback and does not take any arguments.
- prepare_change() has been added, this could have been avoided with a kwarg to
  change_track(track), but that would break more backends.
- core has been updated to call prepare_change+change_track+play as needed.
- tests have been updated to handle this change.

Longer term I hope to completely rework the playback API in backends, as 99% of
our backends only use change_track(track) to translate URIs. So we should make
simple case simple, and handle mopidy-spotify / appsrc in some other way.
Note that since this doesn't use play via change_track we have to copy over the
tracking code and make sure it is only run for the playing case
Same fix as for core.playback.next and mostly the same caveats.
Only handles the playing case, unlike the previous and next changes. It should
also be noted that this is just a temporary state on the road to making this
method handle gapless.
There is still quite a bit to be done is this area:

- We need to start tracking pending tracks as there is time window when we are
  decoding a new track but still playing the old one
  - Currently this breaks seek, next, prev during this time window
- The old on_end_of_track code needs to die.
- Stream changes need to trigger playing events and switch the pending track to
  current.
This has been replaced by on_about_to_finish and on_end_of_stream.
Tests have been updated to reflect these changes.
- Adds stream changed handler to core
- Moves playback started trigger to stream changed
- Made about to finish store next track in _pending_tl_track
- Set the pending track as current in stream changed
- Adds tests for all of this and fixes existing tests
- Pending track should only be triggered by stream_changed if there is one.
- Tracklist changed was incorrectly calling stop breaking tests and gapless
- Tests have been updated to capture and replay audio events. This should avoid
  test deadlocks while still using the audio fakes.
Conflicts:
	mopidy/backend.py
	mopidy/commands.py
	mopidy/core/actor.py
	mopidy/core/playback.py
	tests/audio/test_actor.py
	tests/core/test_playback.py
	tests/local/test_playback.py
Conflicts:
	mopidy/commands.py
	mopidy/core/playback.py
	tests/core/test_playback.py
	tests/local/test_playback.py
This is no longer needed as the plain send method makes sure to use tell to
queue actor message. Which has better performance, and avoids deadlocks.

A side effect of this is that assuming you have a core actor running and a
dummy audio in use audio events just work.
Conflicts:
	tests/local/test_playback.py
…ed in

This is needed in order to make audio events propagate, to core and trigger
async state changes in tests.
@jodal jodal self-assigned this Sep 16, 2015
@jodal jodal added this to the v1.2 - Gapless milestone Sep 16, 2015
self._pending_tl_track = None

if self._audio:
self._audio.set_about_to_finish_callback(self._on_about_to_finish)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this cause the audio actor to run core's code and alter core state?

If you make the callback public, you can probably fix this by using self.core.actor_ref.proxy().playback.on_about_to_finish as the callback.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The call path should be something like the following:

about-to-finish --> audio --> core callback --> backend --> audio 

The goal here is to block the about to finish until an audio.set_uri type call has made it out. (This is also one of the reasons I want backends out of this path). The about-to-finish should be coming from some gst internal thread, so our audio code will also run there, and then from there core would probably still be in the same context to how the callback is done. We den call into the backend via actors, after which the backend calls into audio via actors. So we must ensure that we can't deadlock that last call, or else the about-to-finish never returns.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not exactly easy to follow what's happening here. Are you confident in how this is implemented now, or do I have a point? :-)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fairly sure what I described is correct, and it was intentional to do things outside of actors due to deadlocks that I no longer remember the details of. And also with respect to the circular deps between audio and core setup.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self.core.actor_ref.proxy().playback.on_about_to_finish inside the init actually deadlocks as you are trying to create proxy to yourself while still setting yourself up...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I give it the actor, and then let it create the proxy on the fly in audio it works at least. And I've not manged to deadlock it yet. I still want to think this over a bit more and do some more testing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to create the proxy in on_start(). __init__() is executed in the actor's parent before the actor is actually started, thus there is no ActorRef object to create an ActorProxy from.

@jodal
Copy link
Member

jodal commented Oct 5, 2015

  • Update changelog

When about to finish gets called we are running in some GStreamer thread. Our
audio code then calls the shim core callback which is responsible for
transferring our execution to the core actor thread and waiting for the
response. From this point we do normal actor calls to the backend(s) which in
turn call into the audio actor. Since the initial audio code that was called is
outside the actor this should never deadlock due to this loop.
@adamcik
Copy link
Member Author

adamcik commented Oct 6, 2015

There, now the core code that gets called is in the actor.

@adamcik
Copy link
Member Author

adamcik commented Oct 6, 2015

  • Other followup should probably also be moving certain events out of about-to-finish and into stream changed.

@adamcik
Copy link
Member Author

adamcik commented Oct 7, 2015

Currently blocked on mopidy-spotify / appsrc deadlocking with these changes.

jodal added a commit that referenced this pull request Oct 7, 2015
@jodal jodal merged commit 93899c8 into mopidy:develop Oct 7, 2015
@adamcik adamcik deleted the feature/implement-gapless branch July 27, 2016 21:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants