From e639b2b18ba56051138efa13911aedcf92f7fcc4 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 7 Mar 2015 01:44:35 +0100 Subject: [PATCH 1/7] tests: Add method for emitting fake tags changed in tests --- tests/dummy_audio.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/dummy_audio.py b/tests/dummy_audio.py index 64639e91..b73946cb 100644 --- a/tests/dummy_audio.py +++ b/tests/dummy_audio.py @@ -109,6 +109,10 @@ class DummyAudio(pykka.ThreadingActor): def trigger_fake_playback_failure(self): self._state_change_result = False + def trigger_fake_tags_changed(self, tags): + self._tags = tags + audio.AudioListener.send('tags_changed', tags=self._tags.keys()) + def get_about_to_finish_callback(self): # This needs to be called from outside the actor or we lock up. def wrapper(): From 6fcd43891e7a2b97f3b8681cafd001e61248c96a Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 10 Mar 2015 21:55:51 +0100 Subject: [PATCH 2/7] core: Switch to reference based stream info. - Adds tests for new behaviors in core. - Adds stream name to MPD format (fixes #944) - Adds 'stream_changed' core event (needs a new name/event) - Adds 'get_stream_reference' (which I'm also unsure about) The bits I'm unsure about are mostly with respect to #270, but I'm going ahead with this commit so we can discuss the details in PR with this code as an example. --- mopidy/core/actor.py | 30 +++------ mopidy/core/listener.py | 4 +- mopidy/core/playback.py | 30 +++++---- mopidy/mpd/actor.py | 2 +- mopidy/mpd/protocol/current_playlist.py | 25 +++---- mopidy/mpd/protocol/status.py | 8 +-- mopidy/mpd/translator.py | 6 +- tests/core/test_playback.py | 90 ++++++++++++++++++++++++- 8 files changed, 137 insertions(+), 58 deletions(-) diff --git a/mopidy/core/actor.py b/mopidy/core/actor.py index 19e49838..251f6e2c 100644 --- a/mopidy/core/actor.py +++ b/mopidy/core/actor.py @@ -5,9 +5,8 @@ import itertools import pykka -from mopidy import audio, backend, mixer +from mopidy import audio, backend, mixer, models from mopidy.audio import PlaybackState -from mopidy.audio.utils import convert_tags_to_track from mopidy.core.history import HistoryController from mopidy.core.library import LibraryController from mopidy.core.listener import CoreListener @@ -15,7 +14,6 @@ from mopidy.core.mixer import MixerController from mopidy.core.playback import PlaybackController from mopidy.core.playlists import PlaylistsController from mopidy.core.tracklist import TracklistController -from mopidy.models import TlTrack, Track from mopidy.utils import versioning from mopidy.utils.deprecation import deprecated_property @@ -88,6 +86,9 @@ class Core( def reached_end_of_stream(self): self.playback.on_end_of_track() + def stream_changed(self, uri): + self.playback.on_stream_changed(uri) + def state_changed(self, old_state, new_state, target_state): # XXX: This is a temporary fix for issue #232 while we wait for a more # permanent solution with the implementation of issue #234. When the @@ -116,30 +117,15 @@ class Core( CoreListener.send('mute_changed', mute=mute) def tags_changed(self, tags): - if not self.audio: - return - - current_tl_track = self.playback.get_current_tl_track() - if current_tl_track is None: + if not self.audio or 'title' not in tags: return tags = self.audio.get_current_tags().get() - if not tags: + if not tags or 'title' not in tags or not tags['title']: return - current_track = current_tl_track.track - tags_track = convert_tags_to_track(tags) - - track_kwargs = {k: v for k, v in current_track.__dict__.items() if v} - track_kwargs.update( - {k: v for k, v in tags_track.__dict__.items() if v}) - - self.playback._current_metadata_track = TlTrack(**{ - 'tlid': current_tl_track.tlid, - 'track': Track(**track_kwargs)}) - - # TODO Move this into playback.current_metadata_track setter? - CoreListener.send('current_metadata_changed') + self.playback._stream_ref = models.Ref.track(name=tags['title'][0]) + CoreListener.send('stream_changed') class Backends(list): diff --git a/mopidy/core/listener.py b/mopidy/core/listener.py index 9d952473..f013fa18 100644 --- a/mopidy/core/listener.py +++ b/mopidy/core/listener.py @@ -164,9 +164,9 @@ class CoreListener(listener.Listener): """ pass - def current_metadata_changed(self): + def stream_changed(self): """ - Called whenever current track's metadata changed + Called whenever the currently playing stream changes. *MAY* be implemented by actor. """ diff --git a/mopidy/core/playback.py b/mopidy/core/playback.py index 0d604d61..6314442b 100644 --- a/mopidy/core/playback.py +++ b/mopidy/core/playback.py @@ -20,7 +20,7 @@ class PlaybackController(object): self.core = core self._current_tl_track = None - self._current_metadata_track = None + self._stream_ref = None self._state = PlaybackState.STOPPED def _get_backend(self): @@ -73,20 +73,23 @@ class PlaybackController(object): Use :meth:`get_current_track` instead. """ - def get_current_metadata_track(self): + def get_stream_reference(self): """ - Get a :class:`mopidy.models.TlTrack` with updated metadata for the - currently playing track. + Get additional information about the current stream. - Returns :class:`None` if no track is currently playing. + For most cases this value won't be set, but for radio streams it will + contain a reference with the name of the currently playing track or + program. Clients should show this when available. + + The :class:`mopidy.models.Ref` instance may or may not have an URI set. + If present you can call ``lookup`` on it to get the full metadata for + the URI. + + Returns a :class:`mopidy.models.Ref` instance representing the current + stream. If nothing is playing, or no stream info is available this will + return :class:`None`. """ - return self._current_metadata_track - - current_metadata_track = deprecated_property(get_current_metadata_track) - """ - .. deprecated:: 0.20 - Use :meth:`get_current_metadata_track` instead. - """ + return self._stream_ref def get_state(self): """Get The playback state.""" @@ -244,6 +247,9 @@ class PlaybackController(object): self.stop() self.set_current_tl_track(None) + def on_stream_changed(self, uri): + self._stream_ref = None + def next(self): """ Change to the next track. diff --git a/mopidy/mpd/actor.py b/mopidy/mpd/actor.py index b56e507d..2c63bcb2 100644 --- a/mopidy/mpd/actor.py +++ b/mopidy/mpd/actor.py @@ -74,5 +74,5 @@ class MpdFrontend(pykka.ThreadingActor, CoreListener): def mute_changed(self, mute): self.send_idle('output') - def current_metadata_changed(self): + def stream_changed(self): self.send_idle('playlist') diff --git a/mopidy/mpd/protocol/current_playlist.py b/mopidy/mpd/protocol/current_playlist.py index e083ea7c..fdd65bde 100644 --- a/mopidy/mpd/protocol/current_playlist.py +++ b/mopidy/mpd/protocol/current_playlist.py @@ -276,25 +276,20 @@ def plchanges(context, version): """ # XXX Naive implementation that returns all tracks as changed tracklist_version = context.core.tracklist.version.get() - iversion = int(version) - if iversion < tracklist_version: + if version < tracklist_version: return translator.tracks_to_mpd_format( context.core.tracklist.tl_tracks.get()) - elif iversion == tracklist_version: - # If version are equals, it is just a metadata update - # So we replace the updated track in playlist - current_md_track = context.core.playback.current_metadata_track.get() - if current_md_track is None: + elif version == tracklist_version: + # A version match could indicate this is just a metadata update, so + # check for a stream ref and let the client know about the change. + stream_ref = context.core.playback.get_stream_reference().get() + if stream_ref is None: return None - ntl_tracks = [] - tl_tracks = context.core.tracklist.tl_tracks.get() - for tl_track in tl_tracks: - if tl_track.tlid == current_md_track.tlid: - ntl_tracks.append(current_md_track) - else: - ntl_tracks.append(tl_track) - return translator.tracks_to_mpd_format(ntl_tracks) + tl_track = context.core.playback.current_tl_track.get() + position = context.core.tracklist.index(tl_track).get() + return translator.track_to_mpd_format( + tl_track, position=position, stream=stream_ref) @protocol.commands.add('plchangesposid', version=protocol.INT) diff --git a/mopidy/mpd/protocol/status.py b/mopidy/mpd/protocol/status.py index d33e0afa..e2e73e6f 100644 --- a/mopidy/mpd/protocol/status.py +++ b/mopidy/mpd/protocol/status.py @@ -34,12 +34,12 @@ def currentsong(context): Displays the song info of the current song (same song that is identified in status). """ - tl_track = context.core.playback.current_metadata_track.get() - if tl_track is None: - tl_track = context.core.playback.current_tl_track.get() + tl_track = context.core.playback.current_tl_track.get() + stream = context.core.playback.get_stream_reference().get() if tl_track is not None: position = context.core.tracklist.index(tl_track).get() - return translator.track_to_mpd_format(tl_track, position=position) + return translator.track_to_mpd_format( + tl_track, position=position, stream=stream) @protocol.commands.add('idle', list_command=False) diff --git a/mopidy/mpd/translator.py b/mopidy/mpd/translator.py index 23fb2874..37c1493b 100644 --- a/mopidy/mpd/translator.py +++ b/mopidy/mpd/translator.py @@ -15,7 +15,7 @@ def normalize_path(path, relative=False): return '/'.join(parts) -def track_to_mpd_format(track, position=None): +def track_to_mpd_format(track, position=None, stream=None): """ Format track for output to MPD client. @@ -33,6 +33,7 @@ def track_to_mpd_format(track, position=None): (tlid, track) = track else: (tlid, track) = (None, track) + result = [ ('file', track.uri or ''), ('Time', track.length and (track.length // 1000) or 0), @@ -41,6 +42,9 @@ def track_to_mpd_format(track, position=None): ('Album', track.album and track.album.name or ''), ] + if stream and stream.name != track.name: + result.append(('Name', stream.name)) + if track.date: result.append(('Date', track.date)) diff --git a/tests/core/test_playback.py b/tests/core/test_playback.py index 3b6435c8..15d2d5f8 100644 --- a/tests/core/test_playback.py +++ b/tests/core/test_playback.py @@ -4,8 +4,12 @@ import unittest import mock +import pykka + from mopidy import backend, core -from mopidy.models import Track +from mopidy.models import Ref, Track + +from tests import dummy_audio as audio class CorePlaybackTest(unittest.TestCase): @@ -525,3 +529,87 @@ class CorePlaybackTest(unittest.TestCase): self.assertFalse(self.playback2.get_time_position.called) # TODO Test on_tracklist_change + + +# Since we rely on our DummyAudio to actually emit events we need a "real" +# backend and not a mock so the right calls make it through to audio. +class TestBackend(pykka.ThreadingActor, backend.Backend): + uri_schemes = ['dummy'] + + def __init__(self, config, audio): + super(TestBackend, self).__init__() + self.playback = backend.PlaybackProvider(audio=audio, backend=self) + + +class TestStream(unittest.TestCase): + def setUp(self): # noqa: N802 + self.audio = audio.DummyAudio.start().proxy() + self.backend = TestBackend.start(config={}, audio=self.audio).proxy() + self.core = core.Core(audio=self.audio, backends=[self.backend]) + self.playback = self.core.playback + + self.tracks = [Track(uri='dummy:a', length=1234), + Track(uri='dummy:b', length=1234)] + + self.core.tracklist.add(self.tracks) + + self.events = [] + self.patcher = mock.patch('mopidy.audio.listener.AudioListener.send') + self.send_mock = self.patcher.start() + + def send(event, **kwargs): + self.events.append((event, kwargs)) + + self.send_mock.side_effect = send + + def tearDown(self): # noqa: N802 + pykka.ActorRegistry.stop_all() + self.patcher.stop() + + def replay_audio_events(self): + while self.events: + event, kwargs = self.events.pop(0) + self.core.on_event(event, **kwargs) + + def test_get_stream_reference_before_playback(self): + self.assertEqual(self.playback.get_stream_reference(), None) + + def test_get_stream_reference_during_playback(self): + self.core.playback.play() + + self.replay_audio_events() + self.assertEqual(self.playback.get_stream_reference(), None) + + def test_get_stream_reference_during_playback_with_tags_change(self): + self.core.playback.play() + self.audio.trigger_fake_tags_changed({'title': ['foobar']}).get() + + self.replay_audio_events() + expected = Ref.track(name='foobar') + self.assertEqual(self.playback.get_stream_reference(), expected) + + def test_get_stream_reference_after_next(self): + self.core.playback.play() + self.audio.trigger_fake_tags_changed({'title': ['foobar']}).get() + self.core.playback.next() + + self.replay_audio_events() + self.assertEqual(self.playback.get_stream_reference(), None) + + def test_get_stream_reference_after_next_with_tags_change(self): + self.core.playback.play() + self.audio.trigger_fake_tags_changed({'title': ['foo']}).get() + self.core.playback.next() + self.audio.trigger_fake_tags_changed({'title': ['bar']}).get() + + self.replay_audio_events() + expected = Ref.track(name='bar') + self.assertEqual(self.playback.get_stream_reference(), expected) + + def test_get_stream_reference_after_stop(self): + self.core.playback.play() + self.audio.trigger_fake_tags_changed({'title': ['foobar']}).get() + self.core.playback.stop() + + self.replay_audio_events() + self.assertEqual(self.playback.get_stream_reference(), None) From 4db4b4d63b80510ec25805d3ecb39838c1d76f3b Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 13 Mar 2015 23:56:51 +0100 Subject: [PATCH 3/7] core: Reduce stream metadata to just the title The API I really want for this to support regular tracks, stream updates and dynamic playlists is still unclear to me. As such I'm taking the KISS approach and reducing this to just the stream title and nothing else. If all goes as planed this will be replaced by playback_track_changed(tlid, ref) style events and other improvements in a later version. --- mopidy/core/actor.py | 7 ++++--- mopidy/core/listener.py | 4 ++-- mopidy/core/playback.py | 24 +++++------------------ mopidy/mpd/actor.py | 2 +- mopidy/mpd/protocol/current_playlist.py | 6 +++--- mopidy/mpd/protocol/status.py | 4 ++-- mopidy/mpd/translator.py | 12 +++++------- tests/core/test_playback.py | 26 ++++++++++++------------- 8 files changed, 34 insertions(+), 51 deletions(-) diff --git a/mopidy/core/actor.py b/mopidy/core/actor.py index 251f6e2c..ed1c33ab 100644 --- a/mopidy/core/actor.py +++ b/mopidy/core/actor.py @@ -5,7 +5,7 @@ import itertools import pykka -from mopidy import audio, backend, mixer, models +from mopidy import audio, backend, mixer from mopidy.audio import PlaybackState from mopidy.core.history import HistoryController from mopidy.core.library import LibraryController @@ -124,8 +124,9 @@ class Core( if not tags or 'title' not in tags or not tags['title']: return - self.playback._stream_ref = models.Ref.track(name=tags['title'][0]) - CoreListener.send('stream_changed') + title = tags['title'][0] + self.playback._stream_title = title + CoreListener.send('stream_title_changed', title=title) class Backends(list): diff --git a/mopidy/core/listener.py b/mopidy/core/listener.py index f013fa18..3ae03925 100644 --- a/mopidy/core/listener.py +++ b/mopidy/core/listener.py @@ -164,9 +164,9 @@ class CoreListener(listener.Listener): """ pass - def stream_changed(self): + def stream_title_changed(self, title): """ - Called whenever the currently playing stream changes. + Called whenever the currently playing stream title changes. *MAY* be implemented by actor. """ diff --git a/mopidy/core/playback.py b/mopidy/core/playback.py index 6314442b..e92563dd 100644 --- a/mopidy/core/playback.py +++ b/mopidy/core/playback.py @@ -20,7 +20,7 @@ class PlaybackController(object): self.core = core self._current_tl_track = None - self._stream_ref = None + self._stream_title = None self._state = PlaybackState.STOPPED def _get_backend(self): @@ -73,23 +73,9 @@ class PlaybackController(object): Use :meth:`get_current_track` instead. """ - def get_stream_reference(self): - """ - Get additional information about the current stream. - - For most cases this value won't be set, but for radio streams it will - contain a reference with the name of the currently playing track or - program. Clients should show this when available. - - The :class:`mopidy.models.Ref` instance may or may not have an URI set. - If present you can call ``lookup`` on it to get the full metadata for - the URI. - - Returns a :class:`mopidy.models.Ref` instance representing the current - stream. If nothing is playing, or no stream info is available this will - return :class:`None`. - """ - return self._stream_ref + def get_stream_title(self): + """Get the current stream title or :class:`None`.""" + return self._stream_title def get_state(self): """Get The playback state.""" @@ -248,7 +234,7 @@ class PlaybackController(object): self.set_current_tl_track(None) def on_stream_changed(self, uri): - self._stream_ref = None + self._stream_title = None def next(self): """ diff --git a/mopidy/mpd/actor.py b/mopidy/mpd/actor.py index 2c63bcb2..2aecb6d1 100644 --- a/mopidy/mpd/actor.py +++ b/mopidy/mpd/actor.py @@ -74,5 +74,5 @@ class MpdFrontend(pykka.ThreadingActor, CoreListener): def mute_changed(self, mute): self.send_idle('output') - def stream_changed(self): + def stream_title_changed(self, title): self.send_idle('playlist') diff --git a/mopidy/mpd/protocol/current_playlist.py b/mopidy/mpd/protocol/current_playlist.py index fdd65bde..d8e1a9d8 100644 --- a/mopidy/mpd/protocol/current_playlist.py +++ b/mopidy/mpd/protocol/current_playlist.py @@ -282,14 +282,14 @@ def plchanges(context, version): elif version == tracklist_version: # A version match could indicate this is just a metadata update, so # check for a stream ref and let the client know about the change. - stream_ref = context.core.playback.get_stream_reference().get() - if stream_ref is None: + stream_title = context.core.playback.get_stream_title().get() + if stream_title is None: return None tl_track = context.core.playback.current_tl_track.get() position = context.core.tracklist.index(tl_track).get() return translator.track_to_mpd_format( - tl_track, position=position, stream=stream_ref) + tl_track, position=position, stream_title=stream_title) @protocol.commands.add('plchangesposid', version=protocol.INT) diff --git a/mopidy/mpd/protocol/status.py b/mopidy/mpd/protocol/status.py index e2e73e6f..aa78b387 100644 --- a/mopidy/mpd/protocol/status.py +++ b/mopidy/mpd/protocol/status.py @@ -35,11 +35,11 @@ def currentsong(context): identified in status). """ tl_track = context.core.playback.current_tl_track.get() - stream = context.core.playback.get_stream_reference().get() + stream_title = context.core.playback.get_stream_title().get() if tl_track is not None: position = context.core.tracklist.index(tl_track).get() return translator.track_to_mpd_format( - tl_track, position=position, stream=stream) + tl_track, position=position, stream_title=stream_title) @protocol.commands.add('idle', list_command=False) diff --git a/mopidy/mpd/translator.py b/mopidy/mpd/translator.py index 37c1493b..10207a69 100644 --- a/mopidy/mpd/translator.py +++ b/mopidy/mpd/translator.py @@ -15,7 +15,7 @@ def normalize_path(path, relative=False): return '/'.join(parts) -def track_to_mpd_format(track, position=None, stream=None): +def track_to_mpd_format(track, position=None, stream_title=None): """ Format track for output to MPD client. @@ -23,10 +23,8 @@ def track_to_mpd_format(track, position=None, stream=None): :type track: :class:`mopidy.models.Track` or :class:`mopidy.models.TlTrack` :param position: track's position in playlist :type position: integer - :param key: if we should set key - :type key: boolean - :param mtime: if we should set mtime - :type mtime: boolean + :param stream_title: The current streams title. + :type position: string :rtype: list of two-tuples """ if isinstance(track, TlTrack): @@ -42,8 +40,8 @@ def track_to_mpd_format(track, position=None, stream=None): ('Album', track.album and track.album.name or ''), ] - if stream and stream.name != track.name: - result.append(('Name', stream.name)) + if stream_title: + result.append(('Name', stream_title)) if track.date: result.append(('Date', track.date)) diff --git a/tests/core/test_playback.py b/tests/core/test_playback.py index 15d2d5f8..80efc38a 100644 --- a/tests/core/test_playback.py +++ b/tests/core/test_playback.py @@ -571,45 +571,43 @@ class TestStream(unittest.TestCase): event, kwargs = self.events.pop(0) self.core.on_event(event, **kwargs) - def test_get_stream_reference_before_playback(self): - self.assertEqual(self.playback.get_stream_reference(), None) + def test_get_stream_title_before_playback(self): + self.assertEqual(self.playback.get_stream_title(), None) - def test_get_stream_reference_during_playback(self): + def test_get_stream_title_during_playback(self): self.core.playback.play() self.replay_audio_events() - self.assertEqual(self.playback.get_stream_reference(), None) + self.assertEqual(self.playback.get_stream_title(), None) - def test_get_stream_reference_during_playback_with_tags_change(self): + def test_get_stream_title_during_playback_with_tags_change(self): self.core.playback.play() self.audio.trigger_fake_tags_changed({'title': ['foobar']}).get() self.replay_audio_events() - expected = Ref.track(name='foobar') - self.assertEqual(self.playback.get_stream_reference(), expected) + self.assertEqual(self.playback.get_stream_title(), 'foobar') - def test_get_stream_reference_after_next(self): + def test_get_stream_title_after_next(self): self.core.playback.play() self.audio.trigger_fake_tags_changed({'title': ['foobar']}).get() self.core.playback.next() self.replay_audio_events() - self.assertEqual(self.playback.get_stream_reference(), None) + self.assertEqual(self.playback.get_stream_title(), None) - def test_get_stream_reference_after_next_with_tags_change(self): + def test_get_stream_title_after_next_with_tags_change(self): self.core.playback.play() self.audio.trigger_fake_tags_changed({'title': ['foo']}).get() self.core.playback.next() self.audio.trigger_fake_tags_changed({'title': ['bar']}).get() self.replay_audio_events() - expected = Ref.track(name='bar') - self.assertEqual(self.playback.get_stream_reference(), expected) + self.assertEqual(self.playback.get_stream_title(), 'bar') - def test_get_stream_reference_after_stop(self): + def test_get_stream_title_after_stop(self): self.core.playback.play() self.audio.trigger_fake_tags_changed({'title': ['foobar']}).get() self.core.playback.stop() self.replay_audio_events() - self.assertEqual(self.playback.get_stream_reference(), None) + self.assertEqual(self.playback.get_stream_title(), None) From ea97047607b31303c01cd87ca22ea43ac53182dd Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 14 Mar 2015 00:10:21 +0100 Subject: [PATCH 4/7] flake8: Fix bad import --- tests/core/test_playback.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/test_playback.py b/tests/core/test_playback.py index 80efc38a..8911978a 100644 --- a/tests/core/test_playback.py +++ b/tests/core/test_playback.py @@ -7,7 +7,7 @@ import mock import pykka from mopidy import backend, core -from mopidy.models import Ref, Track +from mopidy.models import Track from tests import dummy_audio as audio From 6d50f835a4a2374a2c8c9635ccd5ff56e35980c5 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 14 Mar 2015 00:22:22 +0100 Subject: [PATCH 5/7] review: docstring update for mpd translator --- mopidy/mpd/translator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mopidy/mpd/translator.py b/mopidy/mpd/translator.py index 10207a69..77adecd0 100644 --- a/mopidy/mpd/translator.py +++ b/mopidy/mpd/translator.py @@ -23,7 +23,7 @@ def track_to_mpd_format(track, position=None, stream_title=None): :type track: :class:`mopidy.models.Track` or :class:`mopidy.models.TlTrack` :param position: track's position in playlist :type position: integer - :param stream_title: The current streams title. + :param stream_title: the current streams title :type position: string :rtype: list of two-tuples """ From 36fe8321b112542661ef20cf5753350df8415f6a Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 14 Mar 2015 00:25:20 +0100 Subject: [PATCH 6/7] docs: Add changelog entry for stream title stuff --- docs/changelog.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index ca36454e..c354f2b9 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -40,6 +40,10 @@ v0.20.0 (UNRELEASED) - Add :meth:`mopidy.core.LibraryController.get_distinct` for getting unique values for a given field. (Fixes: :issue:`913`, PR: :issue:`1022`) +- Add :meth:`mopidy.core.Listener.stream_title_changed` and + :meth:`mopidy.core.PlaybackController.get_stream_title` for letting clients + know about the current song in streams. + **Commands** - Make the ``mopidy`` command print a friendly error message if the @@ -114,6 +118,9 @@ v0.20.0 (UNRELEASED) - Switch the ``list`` command over to using :meth:`mopidy.core.LibraryController.get_distinct`. (Fixes: :issue:`913`) +- Start setting the ``Name`` field which is used for radio streams. + (Fixes: :issue:`944`) + **HTTP frontend** - Prevent race condition in webservice broadcast from breaking the server. From 6260ba00bec4bce4e607ce93ef1fde9aaf2d47f3 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 14 Mar 2015 00:30:46 +0100 Subject: [PATCH 7/7] core: Test stream_title_changed listener --- tests/core/test_listener.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/core/test_listener.py b/tests/core/test_listener.py index 1338ec5e..8ec3a843 100644 --- a/tests/core/test_listener.py +++ b/tests/core/test_listener.py @@ -58,5 +58,5 @@ class CoreListenerTest(unittest.TestCase): def test_listener_has_default_impl_for_seeked(self): self.listener.seeked(0) - def test_listener_has_default_impl_for_current_metadata_changed(self): - self.listener.current_metadata_changed() + def test_listener_has_default_impl_for_stream_title_changed(self): + self.listener.stream_title_changed('foobar')