Merge pull request #207 from jodal/feature/playback-time-position

Use GStreamer playback time position by default
This commit is contained in:
Thomas Adamcik 2012-09-25 13:02:58 -07:00
commit a3bd681c67
6 changed files with 68 additions and 62 deletions

View File

@ -65,6 +65,16 @@ class BasePlaybackProvider(object):
"""
return self.backend.audio.stop_playback().get()
def get_time_position(self):
"""
Get the current time position in milliseconds.
*MAY be reimplemented by subclass.*
:rtype: int
"""
return self.backend.audio.get_position().get()
def get_volume(self):
"""
Get current volume

View File

@ -56,6 +56,7 @@ class DummyLibraryProvider(base.BaseLibraryProvider):
class DummyPlaybackProvider(base.BasePlaybackProvider):
def __init__(self, *args, **kwargs):
super(DummyPlaybackProvider, self).__init__(*args, **kwargs)
self._time_position = 0
self._volume = None
def pause(self):
@ -63,17 +64,22 @@ class DummyPlaybackProvider(base.BasePlaybackProvider):
def play(self, track):
"""Pass None as track to force failure"""
self._time_position = 0
return track is not None
def resume(self):
return True
def seek(self, time_position):
self._time_position = time_position
return True
def stop(self):
return True
def get_time_position(self):
return self._time_position
def get_volume(self):
return self._volume

View File

@ -41,7 +41,7 @@ class LocalBackend(ThreadingActor, base.Backend):
provider=library_provider)
playback_provider = base.BasePlaybackProvider(backend=self)
self.playback = LocalPlaybackController(backend=self,
self.playback = core.PlaybackController(backend=self,
provider=playback_provider)
stored_playlists_provider = LocalStoredPlaylistsProvider(backend=self)
@ -59,18 +59,6 @@ class LocalBackend(ThreadingActor, base.Backend):
self.audio = audio_refs[0].proxy()
class LocalPlaybackController(core.PlaybackController):
def __init__(self, *args, **kwargs):
super(LocalPlaybackController, self).__init__(*args, **kwargs)
# XXX Why do we call stop()? Is it to set GStreamer state to 'READY'?
self.stop()
@property
def time_position(self):
return self.backend.audio.get_position().get()
class LocalStoredPlaylistsProvider(base.BaseStoredPlaylistsProvider):
def __init__(self, *args, **kwargs):
super(LocalStoredPlaylistsProvider, self).__init__(*args, **kwargs)

View File

@ -1,18 +1,35 @@
import logging
import time
from spotify import Link, SpotifyError
from mopidy.backends.base import BasePlaybackProvider
from mopidy.core import PlaybackState
logger = logging.getLogger('mopidy.backends.spotify.playback')
class SpotifyPlaybackProvider(BasePlaybackProvider):
def __init__(self, *args, **kwargs):
super(SpotifyPlaybackProvider, self).__init__(*args, **kwargs)
self._play_time_accumulated = 0
self._play_time_started = 0
def pause(self):
time_since_started = self._wall_time() - self._play_time_started
self._play_time_accumulated += time_since_started
return super(SpotifyPlaybackProvider, self).pause()
def play(self, track):
if self.backend.playback.state == PlaybackState.PLAYING:
self.backend.spotify.session.play(0)
if track.uri is None:
return False
self._play_time_accumulated = 0
self._play_time_started = self._wall_time()
try:
self.backend.spotify.session.load(
Link.from_string(track.uri).as_track())
@ -27,14 +44,37 @@ class SpotifyPlaybackProvider(BasePlaybackProvider):
return False
def resume(self):
self._play_time_started = self._wall_time()
return self.seek(self.backend.playback.time_position)
def seek(self, time_position):
self._play_time_started = self._wall_time()
self._play_time_accumulated = time_position
self.backend.audio.prepare_change()
self.backend.spotify.session.seek(time_position)
self.backend.audio.start_playback()
return True
def stop(self):
self.backend.spotify.session.play(0)
return super(SpotifyPlaybackProvider, self).stop()
def get_time_position(self):
# XXX: The default implementation of get_time_position hangs/times out
# when used with the Spotify backend and GStreamer appsrc. If this can
# be resolved, we no longer need to use a wall clock based time
# position for Spotify playback.
state = self.backend.playback.state
if state == PlaybackState.PLAYING:
time_since_started = (self._wall_time() -
self._play_time_started)
return self._play_time_accumulated + time_since_started
elif state == PlaybackState.PAUSED:
return self._play_time_accumulated
elif state == PlaybackState.STOPPED:
return 0
def _wall_time(self):
return int(time.time() * 1000)

View File

@ -1,6 +1,5 @@
import logging
import random
import time
from mopidy.listeners import BackendListener
@ -20,7 +19,6 @@ def option_wrapper(name, default):
return property(get_option, set_option)
class PlaybackState(object):
"""
Enum of playback states.
@ -87,8 +85,6 @@ class PlaybackController(object):
self._state = PlaybackState.STOPPED
self._shuffled = []
self._first_shuffle = True
self.play_time_accumulated = 0
self.play_time_started = 0
def _get_cpid(self, cp_track):
if cp_track is None:
@ -292,44 +288,10 @@ class PlaybackController(object):
self._trigger_playback_state_changed(old_state, new_state)
# FIXME play_time stuff assumes backend does not have a better way of
# handeling this stuff :/
if (old_state in (PlaybackState.PLAYING, PlaybackState.STOPPED)
and new_state == PlaybackState.PLAYING):
self._play_time_start()
elif (old_state == PlaybackState.PLAYING
and new_state == PlaybackState.PAUSED):
self._play_time_pause()
elif (old_state == PlaybackState.PAUSED
and new_state == PlaybackState.PLAYING):
self._play_time_resume()
@property
def time_position(self):
"""Time position in milliseconds."""
if self.state == PlaybackState.PLAYING:
time_since_started = (self._current_wall_time -
self.play_time_started)
return self.play_time_accumulated + time_since_started
elif self.state == PlaybackState.PAUSED:
return self.play_time_accumulated
elif self.state == PlaybackState.STOPPED:
return 0
def _play_time_start(self):
self.play_time_accumulated = 0
self.play_time_started = self._current_wall_time
def _play_time_pause(self):
time_since_started = self._current_wall_time - self.play_time_started
self.play_time_accumulated += time_since_started
def _play_time_resume(self):
self.play_time_started = self._current_wall_time
@property
def _current_wall_time(self):
return int(time.time() * 1000)
return self.provider.get_time_position()
@property
def volume(self):
@ -490,9 +452,6 @@ class PlaybackController(object):
self.next()
return True
self.play_time_started = self._current_wall_time
self.play_time_accumulated = time_position
success = self.provider.seek(time_position)
if success:
self._trigger_seeked(time_position)

View File

@ -159,18 +159,21 @@ class StatusHandlerTest(unittest.TestCase):
self.assertLessEqual(position, total)
def test_status_method_when_playing_contains_elapsed(self):
self.backend.playback.state = PAUSED
self.backend.playback.play_time_accumulated = 59123
self.backend.current_playlist.append([Track(length=60000)])
self.backend.playback.play()
self.backend.playback.pause()
self.backend.playback.seek(59123)
result = dict(status.status(self.context))
self.assertIn('elapsed', result)
self.assertEqual(result['elapsed'], '59.123')
def test_status_method_when_starting_playing_contains_elapsed_zero(self):
self.backend.playback.state = PAUSED
self.backend.playback.play_time_accumulated = 123 # Less than 1000ms
self.backend.current_playlist.append([Track(length=10000)])
self.backend.playback.play()
self.backend.playback.pause()
result = dict(status.status(self.context))
self.assertIn('elapsed', result)
self.assertEqual(result['elapsed'], '0.123')
self.assertEqual(result['elapsed'], '0.000')
def test_status_method_when_playing_contains_bitrate(self):
self.backend.current_playlist.append([Track(bitrate=320)])