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

Fix latency stream #1845

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion mopidy/audio/actor.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ def __init__(self, config, mixer):
self._config = config
self._target_state = Gst.State.NULL
self._buffering = False
self._live_stream = False
self._tags = {}
self._pending_uri = None
self._pending_tags = None
Expand Down Expand Up @@ -572,16 +573,23 @@ def _on_source_setup(self, element, source):
else:
self._appsrc.reset()

if self._live_stream and hasattr(source.props, "is_live"):
gst_logger.debug("HTTP Src - setting live mode")
lukh marked this conversation as resolved.
Show resolved Hide resolved
source.set_live(True)

utils.setup_proxy(source, self._config["proxy"])

def set_uri(self, uri):
def set_uri(self, uri, live_stream=False):
lukh marked this conversation as resolved.
Show resolved Hide resolved
"""
Set URI of audio to be played.

You *MUST* call :meth:`prepare_change` before calling this method.

:param uri: the URI to play
:type uri: string
:param live_stream: disable buffering, reducing latency for stream,
and discarding data when paused
:type live_stream: bool
"""

# XXX: Hack to workaround issue on Mac OS X where volume level
Expand All @@ -593,6 +601,7 @@ def set_uri(self, uri):

self._pending_uri = uri
self._pending_tags = {}
self._live_stream = live_stream
self._playbin.set_property("uri", uri)

if self.mixer is not None and current_volume is not None:
Expand Down
18 changes: 17 additions & 1 deletion mopidy/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,22 @@ def translate_uri(self, uri):
"""
return uri

def is_live(self, uri):
"""
Defines if the custom URI scheme should be read as a live stream
lukh marked this conversation as resolved.
Show resolved Hide resolved

*MAY be reimplemented by subclass.*

Playing a source as a live stream
disable buffering, reducing latency for stream,
lukh marked this conversation as resolved.
Show resolved Hide resolved
and discarding data when paused.
lukh marked this conversation as resolved.
Show resolved Hide resolved

:param uri: the custom URI
:type uri: string
:rtype: bool
"""
return False

def change_track(self, track):
"""
Swith to provided track.
Expand All @@ -235,7 +251,7 @@ def change_track(self, track):
logger.debug("Backend translated URI from %s to %s", track.uri, uri)
if not uri:
return False
self.audio.set_uri(uri).get()
self.audio.set_uri(uri, live_stream=self.is_live(uri)).get()
return True

def resume(self):
Expand Down
51 changes: 51 additions & 0 deletions tests/audio/test_actor.py
Original file line number Diff line number Diff line change
Expand Up @@ -629,3 +629,54 @@ def test_change_to_stopped_while_buffering(self):
self.audio.stop_playback()
playbin.set_state.assert_called_with(Gst.State.NULL)
assert not self.audio._buffering


class AudioLiveTest(unittest.TestCase):
config = {
"audio": {
"buffer_time": None,
"mixer": "fakemixer track_max_volume=65536",
"mixer_track": None,
"mixer_volume": None,
"output": "testoutput",
"visualizer": None,
}
}

def setUp(self): # noqa: N802
config = {
"audio": {
"buffer_time": None,
"mixer": "foomixer",
"mixer_volume": None,
"output": "testoutput",
"visualizer": None,
},
"proxy": {"hostname": ""},
}
self.audio = audio.Audio(config=config, mixer=None)

def test_not_live_mode(self):
source = mock.MagicMock()

# disable appsrc configure
lukh marked this conversation as resolved.
Show resolved Hide resolved
source.get_factory.get_name = mock.MagicMock(return_value="not_appsrc")
source.props = mock.MagicMock(spec=[])
self.audio._live_stream = False

self.audio._on_source_setup("dummy", source)

source.set_live.assert_not_called()

def test_live_mode(self):
source = mock.MagicMock()

# disable appsrc configure
source.get_factory.get_name = mock.MagicMock(return_value="not_appsrc")

source.props.is_live = mock.MagicMock(return_value=True)
self.audio._live_stream = True

self.audio._on_source_setup("dummy", source)

source.set_live.assert_called_with(True)
3 changes: 3 additions & 0 deletions tests/core/test_playback.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@ def test_play_tlid(self):
current_tl_track = self.core.playback.get_current_tl_track()
assert tl_tracks[1] == current_tl_track

def test_default_is_live_behaviour_is_not_live(self):
assert not self.backend.playback.is_live(self.tracks[0].uri).get()


class TestNextHandling(BaseTest):
def test_get_current_tl_track_next(self):
Expand Down
4 changes: 3 additions & 1 deletion tests/dummy_audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,16 @@ def __init__(self, config=None, mixer=None):
self._callback = None
self._uri = None
self._stream_changed = False
self._live_stream = False
self._tags = {}
self._bad_uris = set()

def set_uri(self, uri):
def set_uri(self, uri, live_stream=False):
assert self._uri is None, "prepare change not called before set"
self._position = 0
self._uri = uri
self._stream_changed = True
self._live_stream = live_stream
self._tags = {}

def set_appsrc(self, *args, **kwargs):
Expand Down