diff --git a/mopidy/backends/libspotify/__init__.py b/mopidy/backends/libspotify/__init__.py index b098d0ac..1fa07836 100644 --- a/mopidy/backends/libspotify/__init__.py +++ b/mopidy/backends/libspotify/__init__.py @@ -1,14 +1,9 @@ import datetime as dt -import gobject import logging import os import multiprocessing import threading -import pygst -pygst.require('0.10') -import gst - from spotify import Link, SpotifyError from spotify.manager import SpotifySessionManager from spotify.alsahelper import AlsaController @@ -18,6 +13,7 @@ from mopidy.backends.base import (BaseBackend, BaseCurrentPlaylistController, BaseLibraryController, BasePlaybackController, BaseStoredPlaylistsController) from mopidy.models import Artist, Album, Track, Playlist +from mopidy.process import pickle_connection logger = logging.getLogger('mopidy.backends.libspotify') @@ -47,7 +43,6 @@ class LibspotifyBackend(BaseBackend): self.stored_playlists = LibspotifyStoredPlaylistsController( backend=self) self.uri_handlers = [u'spotify:', u'http://open.spotify.com/'] - self.gstreamer_pipeline = gst.Pipeline("spotify_pipeline") self.spotify = self._connect() def _connect(self): @@ -55,7 +50,7 @@ class LibspotifyBackend(BaseBackend): spotify = LibspotifySessionManager( settings.SPOTIFY_USERNAME, settings.SPOTIFY_PASSWORD, core_queue=self.core_queue, - gstreamer_pipeline=self.gstreamer_pipeline) + output_connection=self.output_connection) spotify.start() return spotify @@ -97,13 +92,22 @@ class LibspotifyLibraryController(BaseLibraryController): class LibspotifyPlaybackController(BasePlaybackController): + def _set_output_state(self, state_name): + logger.debug(u'Setting output state to %s ...', state_name) + (my_end, other_end) = multiprocessing.Pipe() + self.backend.output_connection.send({ + 'command': 'set_state', + 'state': state_name, + 'reply_to': pickle_connection(other_end), + }) + my_end.poll(None) + return my_end.recv() + def _pause(self): - result = self.backend.gstreamer_pipeline.set_state(gst.STATE_PAUSED) - logger.debug('Changed gstreamer state to paused. Result was: %s' % result) - return result == gst.STATE_CHANGE_SUCCESS + return self._set_output_state('PAUSED') def _play(self, track): - self.backend.gstreamer_pipeline.set_state(gst.STATE_READY) + self._set_output_state('READY') if self.state == self.PLAYING: self.stop() if track.uri is None: @@ -112,24 +116,22 @@ class LibspotifyPlaybackController(BasePlaybackController): self.backend.spotify.session.load( Link.from_string(track.uri).as_track()) self.backend.spotify.session.play(1) - self.backend.gstreamer_pipeline.set_state(gst.STATE_PLAYING) + self._set_output_state('PLAYING') return True except SpotifyError as e: logger.warning('Play %s failed: %s', track.uri, e) return False def _resume(self): - result = self.backend.gstreamer_pipeline.set_state(gst.STATE_PLAYING) - logger.debug('Changed gstreamer state to playing. Result was: %s' % result) - return result == gst.STATE_CHANGE_SUCCESS + return self._set_output_state('PLAYING') def _seek(self, time_position): pass # TODO def _stop(self): - self.backend.gstreamer_pipeline.set_state(gst.STATE_READY) + result = self._set_output_state('READY') self.backend.spotify.session.play(0) - return True + return result class LibspotifyStoredPlaylistsController(BaseStoredPlaylistsController): @@ -202,57 +204,19 @@ class LibspotifyTranslator(object): tracks=[cls.to_mopidy_track(t) for t in spotify_playlist], ) -class GstreamerMessageBusProcess(threading.Thread): - def __init__(self, core_queue, pipeline): - super(GstreamerMessageBusProcess, self).__init__() - self.core_queue = core_queue - self.bus = pipeline.get_bus() - - def run(self): - loop = gobject.MainLoop() - gobject.threads_init() - context = loop.get_context() - while True: - message = self.bus.pop_filtered(gst.MESSAGE_EOS) - if message is not None: - self.core_queue.put({'command': 'end_of_track'}) - logger.debug('Got and handled Gstreamer message of type: %s' % message.type) - context.iteration(True) - class LibspotifySessionManager(SpotifySessionManager, threading.Thread): cache_location = os.path.expanduser(settings.SPOTIFY_LIB_CACHE) settings_location = os.path.expanduser(settings.SPOTIFY_LIB_CACHE) appkey_file = os.path.expanduser(settings.SPOTIFY_LIB_APPKEY) user_agent = 'Mopidy %s' % get_version() - def __init__(self, username, password, core_queue, gstreamer_pipeline): + def __init__(self, username, password, core_queue, output_connection): SpotifySessionManager.__init__(self, username, password) threading.Thread.__init__(self) self.core_queue = core_queue + self.output_connection = output_connection self.connected = threading.Event() self.session = None - self.gstreamer_pipeline = gstreamer_pipeline - - cap_string = """audio/x-raw-int, - endianness=(int)1234, - channels=(int)2, - width=(int)16, - depth=(int)16, - signed=True, - rate=(int)44100""" - caps = gst.caps_from_string(cap_string) - - self.gsrc = gst.element_factory_make("appsrc", "app-source") - self.gsrc.set_property('caps', caps) - - self.gsink = gst.element_factory_make("autoaudiosink", "autosink") - - self.gstreamer_pipeline.add(self.gsrc, self.gsink) - - gst.element_link_many(self.gsrc, self.gsink) - - message_process = GstreamerMessageBusProcess(self.core_queue, self.gstreamer_pipeline) - message_process.start() def run(self): self.connect() @@ -294,17 +258,21 @@ class LibspotifySessionManager(SpotifySessionManager, threading.Thread): def music_delivery(self, session, frames, frame_size, num_frames, sample_type, sample_rate, channels): """Callback used by pyspotify""" - cap_string = """audio/x-raw-int, - endianness=(int)1234, - channels=(int)2, - width=(int)16, - depth=(int)16, - signed=True, - rate=(int)44100""" - caps = gst.caps_from_string(cap_string) - b = gst.Buffer(frames) - b.set_caps(caps) - self.gsrc.emit('push-buffer', b) + # TODO Base caps_string on arguments + caps_string = """ + audio/x-raw-int, + endianness=(int)1234, + channels=(int)2, + width=(int)16, + depth=(int)16, + signed=True, + rate=(int)44100 + """ + self.output_connection.send({ + 'command': 'deliver_data', + 'caps': caps_string, + 'data': bytes(frames), + }) def play_token_lost(self, session): """Callback used by pyspotify""" @@ -317,9 +285,8 @@ class LibspotifySessionManager(SpotifySessionManager, threading.Thread): def end_of_track(self, session): """Callback used by pyspotify""" - logger.debug('End of track.') - self.gsrc.emit('end-of-stream') - logger.debug('End of stream sent to gstreamer.') + logger.debug('End of data stream.') + self.output_connection.send({'command': 'end_of_data_stream'}) def search(self, query, connection): """Search method used by Mopidy backend"""