Skip to content

Commit a6af848

Browse files
committed
Needs a commit message
1 parent 7be0d1b commit a6af848

File tree

2 files changed

+33
-34
lines changed

2 files changed

+33
-34
lines changed

mopidy_spotify/playback.py

+18-20
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import functools
2+
import collections
23
import logging
34
import threading
45

@@ -33,7 +34,7 @@ def __init__(self, *args, **kwargs):
3334
self._push_audio_data_event.set()
3435
self._end_of_track_event = threading.Event()
3536
self._events_connected = False
36-
self._held_buffer = BufferData()
37+
self._held_buffers = collections.deque()
3738

3839
def _connect_events(self):
3940
if not self._events_connected:
@@ -45,7 +46,7 @@ def _connect_events(self):
4546
self._seeking_event,
4647
self._push_audio_data_event,
4748
self._buffer_timestamp,
48-
self._held_buffer,
49+
self._held_buffers,
4950
)
5051
self.backend._session.on(
5152
spotify.SessionEvent.END_OF_TRACK,
@@ -80,8 +81,8 @@ def change_track(self, track):
8081
self._first_seek = True
8182
self._end_of_track_event.clear()
8283

83-
# Discard held buffer
84-
self._held_buffer.data = None
84+
# Discard held buffers
85+
self._held_buffers.clear()
8586

8687
try:
8788
sp_track = self.backend._session.get_track(track.uri)
@@ -168,7 +169,7 @@ def music_delivery_callback(
168169
seeking_event,
169170
push_audio_data_event,
170171
buffer_timestamp,
171-
held_buffer,
172+
held_buffers,
172173
):
173174
# This is called from an internal libspotify thread.
174175
# Ideally, nothing here should block.
@@ -199,21 +200,25 @@ def music_delivery_callback(
199200
)
200201

201202
# If there is any held buffer, send it
202-
# change_track() in other thread could clobber before we emit_data. OK?
203-
if held_buffer.data is not None:
204-
consumed = audio_actor.emit_data(held_buffer.data).get()
203+
if held_buffers:
204+
while held_buffers:
205+
buf = held_buffers.popleft()
206+
consumed = audio_actor.emit_data(buf).get()
207+
if not consumed:
208+
held_buffers.appendleft(buf)
209+
break
205210
else:
206-
# No buffer, we assume successful consumption
211+
# No held buffer, don't apply back-pressure
207212
consumed = True
208213

209-
# Hold the buffer for a while, so we can filter out the single empty buffer
210-
# after the track
211-
held_buffer.data = buffer_
212-
213214
if consumed:
215+
# Held buffer consumed, holding the next buffer. This is to drop the last
216+
# buffer we receieve for the track which does not contain audio data.
217+
held_buffers.append(buffer_)
214218
buffer_timestamp.increase(duration)
215219
return num_frames
216220
else:
221+
# Pass back-pressure on to libspotify, new buffer will be redelivered
217222
return 0
218223

219224

@@ -254,10 +259,3 @@ def set(self, value):
254259
def increase(self, value):
255260
with self._lock:
256261
self._value += value
257-
258-
259-
class BufferData:
260-
"""Wrapper around an Gst.Buffer to pass around."""
261-
262-
def __init__(self, buf=None):
263-
self.data = buf

tests/test_playback.py

+15-14
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import collections
12
import threading
23
from unittest import mock
34

@@ -64,7 +65,7 @@ def test_connect_events_adds_music_delivery_handler_to_session(
6465
playback_provider._seeking_event,
6566
playback_provider._push_audio_data_event,
6667
playback_provider._buffer_timestamp,
67-
playback_provider._held_buffer,
68+
playback_provider._held_buffers,
6869
)
6970
in session_mock.on.call_args_list
7071
)
@@ -137,14 +138,14 @@ def test_change_track_clears_state(audio_mock, provider):
137138
provider._first_seek = False
138139
provider._buffer_timestamp.set(99)
139140
provider._end_of_track_event.set()
140-
provider._held_buffer.data = mock.sentinel.gst_buffer
141+
provider._held_buffers = collections.deque([mock.sentinel.gst_buffer])
141142

142143
assert provider.change_track(track) is True
143144

144145
assert provider._first_seek
145146
assert provider._buffer_timestamp.get() == 0
146147
assert not provider._end_of_track_event.is_set()
147-
assert provider._held_buffer.data is None
148+
assert len(provider._held_buffers) == 0
148149

149150

150151
def test_resume_starts_spotify_playback(session_mock, provider):
@@ -225,7 +226,7 @@ def test_music_delivery_rejects_data_when_seeking(session_mock, audio_mock):
225226
push_audio_data_event = threading.Event()
226227
push_audio_data_event.set()
227228
buffer_timestamp = mock.Mock()
228-
held_buffer = playback.BufferData(mock.sentinel.gst_buffer)
229+
held_buffer = collections.deque([mock.sentinel.gst_buffer])
229230
assert seeking_event.is_set()
230231

231232
result = playback.music_delivery_callback(
@@ -257,7 +258,7 @@ def test_music_delivery_when_seeking_accepts_data_after_empty_delivery(
257258
push_audio_data_event = threading.Event()
258259
push_audio_data_event.set()
259260
buffer_timestamp = mock.Mock()
260-
held_buffer = playback.BufferData(mock.sentinel.gst_buffer)
261+
held_buffer = collections.deque([mock.sentinel.gst_buffer])
261262
assert seeking_event.is_set()
262263

263264
result = playback.music_delivery_callback(
@@ -287,7 +288,7 @@ def test_music_delivery_rejects_data_depending_on_push_audio_data_event(
287288
seeking_event = threading.Event()
288289
push_audio_data_event = threading.Event()
289290
buffer_timestamp = mock.Mock()
290-
held_buffer = playback.BufferData(mock.sentinel.gst_buffer)
291+
held_buffer = collections.deque([mock.sentinel.gst_buffer])
291292
assert not push_audio_data_event.is_set()
292293

293294
result = playback.music_delivery_callback(
@@ -317,7 +318,7 @@ def test_music_delivery_shortcuts_if_no_data_in_frames(
317318
push_audio_data_event = threading.Event()
318319
push_audio_data_event.set()
319320
buffer_timestamp = mock.Mock()
320-
held_buffer = playback.BufferData(mock.sentinel.gst_buffer)
321+
held_buffer = collections.deque([mock.sentinel.gst_buffer])
321322

322323
result = playback.music_delivery_callback(
323324
session_mock,
@@ -345,7 +346,7 @@ def test_music_delivery_rejects_unknown_audio_formats(session_mock, audio_mock):
345346
push_audio_data_event = threading.Event()
346347
push_audio_data_event.set()
347348
buffer_timestamp = mock.Mock()
348-
held_buffer = playback.BufferData(mock.sentinel.gst_buffer)
349+
held_buffer = collections.deque([mock.sentinel.gst_buffer])
349350

350351
with pytest.raises(AssertionError) as excinfo:
351352
playback.music_delivery_callback(
@@ -378,7 +379,7 @@ def test_music_delivery_creates_gstreamer_buffer_and_holds_it(
378379
push_audio_data_event.set()
379380
buffer_timestamp = mock.Mock()
380381
buffer_timestamp.get.return_value = mock.sentinel.timestamp
381-
held_buffer = playback.BufferData(None)
382+
held_buffer = collections.deque()
382383

383384
result = playback.music_delivery_callback(
384385
session_mock,
@@ -401,7 +402,7 @@ def test_music_delivery_creates_gstreamer_buffer_and_holds_it(
401402
buffer_timestamp.increase.assert_called_once_with(mock.sentinel.duration)
402403
audio_mock.emit_data.assert_not_called()
403404
assert result == num_frames
404-
assert held_buffer.data == mock.sentinel.gst_buffer
405+
assert list(held_buffer) == [mock.sentinel.gst_buffer]
405406

406407

407408
def test_music_delivery_gives_held_buffer_to_audio_and_holds_created(
@@ -416,7 +417,7 @@ def test_music_delivery_gives_held_buffer_to_audio_and_holds_created(
416417
push_audio_data_event = threading.Event()
417418
push_audio_data_event.set()
418419
buffer_timestamp = mock.Mock()
419-
held_buffer = playback.BufferData(mock.sentinel.gst_buffer1)
420+
held_buffer = collections.deque([mock.sentinel.gst_buffer1])
420421

421422
result = playback.music_delivery_callback(
422423
session_mock,
@@ -431,7 +432,7 @@ def test_music_delivery_gives_held_buffer_to_audio_and_holds_created(
431432
)
432433
assert result == num_frames
433434
audio_mock.emit_data.assert_called_once_with(mock.sentinel.gst_buffer1)
434-
assert held_buffer.data == mock.sentinel.gst_buffer2
435+
assert list(held_buffer) == [mock.sentinel.gst_buffer2]
435436

436437

437438
def test_music_delivery_consumes_zero_frames_if_audio_fails(
@@ -449,7 +450,7 @@ def test_music_delivery_consumes_zero_frames_if_audio_fails(
449450
push_audio_data_event.set()
450451
buffer_timestamp = mock.Mock()
451452
buffer_timestamp.get.return_value = mock.sentinel.timestamp
452-
held_buffer = playback.BufferData(mock.sentinel.gst_buffer)
453+
held_buffer = collections.deque([mock.sentinel.gst_buffer1])
453454

454455
result = playback.music_delivery_callback(
455456
session_mock,
@@ -465,7 +466,7 @@ def test_music_delivery_consumes_zero_frames_if_audio_fails(
465466

466467
assert buffer_timestamp.increase.call_count == 0
467468
assert result == 0
468-
assert held_buffer.data == mock.sentinel.gst_buffer2 # SPONG
469+
assert list(held_buffer) == [mock.sentinel.gst_buffer1]
469470

470471

471472
def test_end_of_track_callback(session_mock, audio_mock):

0 commit comments

Comments
 (0)