+ if (_pending_seek_position || _disable_audio) {
+ /* Don't store any audio in these cases */
+ return;
+ }
+ }
+
+ boost::mutex::scoped_lock lm2 (_buffers_mutex);
+ _audio.put (remap (audio, _audio_channels, _audio_mapping), time);
+}
+
+/** Try to get `frames' frames of audio and copy it into `out'. Silence
+ * will be filled if no audio is available.
+ * @return time of this audio, or unset if there was a buffer underrun.
+ */
+optional<DCPTime>
+Butler::get_audio (float* out, Frame frames)
+{
+ optional<DCPTime> t = _audio.get (out, _audio_channels, frames);
+ _summon.notify_all ();
+ return t;
+}
+
+void
+Butler::disable_audio ()
+{
+ boost::mutex::scoped_lock lm (_mutex);
+ _disable_audio = true;
+}
+
+pair<size_t, string>
+Butler::memory_used () const
+{
+ /* XXX: should also look at _audio.memory_used() */
+ return _video.memory_used();
+}
+
+void
+Butler::player_change (ChangeType type, bool frequent)
+{
+ boost::mutex::scoped_lock lm (_mutex);
+
+ if (type == CHANGE_TYPE_PENDING) {
+ ++_suspended;
+ } else if (type == CHANGE_TYPE_DONE) {
+ --_suspended;
+ if (_died || _pending_seek_position || frequent) {
+ lm.unlock ();
+ _summon.notify_all ();
+ return;
+ }
+
+ DCPTime seek_to;
+ DCPTime next = _video.get().second;
+ if (_awaiting && _awaiting > next) {
+ /* We have recently done a player_changed seek and our buffers haven't been refilled yet,
+ so assume that we're seeking to the same place as last time.
+ */
+ seek_to = *_awaiting;
+ } else {
+ seek_to = next;
+ }
+
+ seek_unlocked (seek_to, true);
+ _awaiting = seek_to;
+ } else if (type == CHANGE_TYPE_CANCELLED) {
+ --_suspended;