+ boost::mutex::scoped_lock lm (_mutex);
+
+ if (_pending_seek_position) {
+ /* Don't store any video in this case */
+ return;
+ }
+
+ _prepare_service.post (bind(&Butler::prepare, this, weak_ptr<PlayerVideo>(video)));
+
+ _video.put (video, time);
+}
+
+
+void
+Butler::audio (shared_ptr<AudioBuffers> audio, DCPTime time, int frame_rate)
+{
+ boost::mutex::scoped_lock lm (_mutex);
+ if (_pending_seek_position || _disable_audio) {
+ /* Don't store any audio in these cases */
+ return;
+ }
+
+ _audio.put (remap(audio, _audio_channels, _audio_mapping), time, frame_rate);
+}
+
+
+/** Try to get `frames' frames of audio and copy it into `out'.
+ * @param behaviour BLOCKING if we should block until audio is available. If behaviour is NON_BLOCKING
+ * and no audio is immediately available the buffer will be filled with silence and boost::none
+ * will be returned.
+ * @return time of this audio, or unset if blocking was false and no data was available.
+ */
+optional<DCPTime>
+Butler::get_audio (Behaviour behaviour, float* out, Frame frames)
+{
+ boost::mutex::scoped_lock lm (_mutex);
+
+ while (behaviour == Behaviour::BLOCKING && !_finished && !_died && _audio.size() < frames) {
+ _arrived.wait (lm);
+ }
+
+ auto t = _audio.get (out, _audio_channels, frames);
+ _summon.notify_all ();
+ return t;
+}
+
+
+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, int property)
+{
+ if (property == VideoContentProperty::CROP) {
+ if (type == ChangeType::DONE) {
+ auto film = _film.lock();
+ if (film) {
+ _video.reset_metadata(film, _player.video_container_size());
+ }
+ }
+ return;
+ }
+
+ boost::mutex::scoped_lock lm (_mutex);
+
+ if (type == ChangeType::PENDING) {
+ ++_suspended;
+ } else if (type == ChangeType::DONE) {
+ --_suspended;
+ if (_died || _pending_seek_position) {
+ lm.unlock ();
+ _summon.notify_all ();