[gnome-music/wip/mschraal/player-factor-out-gstreamer] Add smoothscale and start to use it



commit 749237f515f6e6d2c9d4771ead8986d1d957b486
Author: Marinus Schraal <mschraal gnome org>
Date:   Fri Feb 16 15:15:29 2018 +0100

    Add smoothscale and start to use it

 data/PlayerToolbar.ui             |   2 +-
 gnomemusic/player.py              | 201 ++++-------------------------------
 gnomemusic/widgets/Makefile.am    |   1 +
 gnomemusic/widgets/smoothscale.py | 218 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 241 insertions(+), 181 deletions(-)
---
diff --git a/data/PlayerToolbar.ui b/data/PlayerToolbar.ui
index f42f256..d439bd2 100644
--- a/data/PlayerToolbar.ui
+++ b/data/PlayerToolbar.ui
@@ -191,7 +191,7 @@
       </object>
     </child>
     <child>
-      <object class="GtkScale" id="progress_scale">
+      <object class="SmoothScale" id="smooth_scale">
         <property name="visible">True</property>
         <property name="can_focus">True</property>
         <property name="valign">center</property>
diff --git a/gnomemusic/player.py b/gnomemusic/player.py
index 5ca1389..0b7c1e0 100644
--- a/gnomemusic/player.py
+++ b/gnomemusic/player.py
@@ -49,6 +49,7 @@ from gnomemusic.grilo import grilo
 from gnomemusic.playlists import Playlists
 from gnomemusic.scrobbler import LastFmScrobbler
 from gnomemusic.widgets.coverstack import CoverStack
+from gnomemusic.widgets.smoothscale import SmoothScale
 import gnomemusic.utils as utils
 
 
@@ -114,7 +115,6 @@ class Player(GObject.GObject):
         self._settings.connect(
             'changed::repeat', self._on_repeat_setting_changed)
         self.repeat = self._settings.get_enum('repeat')
-        self._setup_view()
 
         self.playlist_insert_handler = 0
         self.playlist_delete_handler = 0
@@ -123,10 +123,7 @@ class Player(GObject.GObject):
         self._player.connect('eos', self._on_eos)
         self._player.connect('notify::state', self._on_state_change)
 
-        self.timeout = None
-        self._seconds_period = 0
-        self._seconds_timeout = 0
-
+        self._setup_view()
         self._lastfm = LastFmScrobbler()
 
     def discover_item(self, item, callback, data=None):
@@ -368,7 +365,7 @@ class Player(GObject.GObject):
 
     @log
     def load(self, media):
-        self._progress_scale_zero()
+        self._progress_scale._progress_scale_zero()
         self._set_duration(media.get_duration())
         self._total_time_label.set_label(
             utils.seconds_to_string(media.get_duration()))
@@ -448,7 +445,7 @@ class Player(GObject.GObject):
         elif (self.repeat == RepeatType.NONE):
             self.stop()
             self._play_button.set_image(self._play_image)
-            self._progress_scale_zero()
+            self._progress_scale._progress_scale_zero()
             self._progress_scale.set_sensitive(False)
             if self.playlist is not None:
                 current_track = self.playlist.get_path(
@@ -468,7 +465,7 @@ class Player(GObject.GObject):
             # Stop playback
             self.stop()
             self._play_button.set_image(self._play_image)
-            self._progress_scale_zero()
+            self._progress_scale._progress_scale_zero()
             self._progress_scale.set_sensitive(False)
             self.emit('playback-status-changed')
 
@@ -490,24 +487,24 @@ class Player(GObject.GObject):
 
         self._player.state = Playback.PLAYING
 
-        self._update_position_callback()
+        self._progress_scale._update_position_callback()
         if media:
             self._lastfm.now_playing(media)
-        if not self.timeout and self._progress_scale.get_realized():
-            self._update_timeout()
+        if not self._progress_scale.timeout and self._progress_scale.get_realized():
+            self._progress_scale._update_timeout()
 
         self.emit('playback-status-changed')
 
     @log
     def pause(self):
-        self._remove_timeout()
+        self._progress_scale._remove_timeout()
 
         self._player.state = Playback.PAUSED
         self.emit('playback-status-changed')
 
     @log
     def stop(self):
-        self._remove_timeout()
+        self._progress_scale._remove_timeout()
 
         self._player.state = Playback.STOPPED
         self.emit('playback-status-changed')
@@ -538,8 +535,8 @@ class Player(GObject.GObject):
 
         position = self._player.position
         if position >= 5:
-            self._progress_scale_zero()
-            self.on_progress_scale_change_value(self._progress_scale)
+            self._progress_scale._progress_scale_zero()
+            self._progress_scale._on_progress_scale_change_value(self._progress_scale)
             return
 
         self.stop()
@@ -609,7 +606,12 @@ class Player(GObject.GObject):
         self._next_button = self._ui.get_object('next_button')
         self._play_image = self._ui.get_object('play_image')
         self._pause_image = self._ui.get_object('pause_image')
-        self._progress_scale = self._ui.get_object('progress_scale')
+
+        self._progress_scale = self._ui.get_object('smooth_scale')
+        self._progress_scale._player = self._player
+
+        self._progress_scale.connect('seek-finished', self._on_seek_finished)
+
         self._progress_time_label = self._ui.get_object('playback')
         self._total_time_label = self._ui.get_object('duration')
         self._title_label = self._ui.get_object('title')
@@ -627,125 +629,10 @@ class Player(GObject.GObject):
         self._prev_button.connect('clicked', self._on_prev_button_clicked)
         self._play_button.connect('clicked', self._on_play_button_clicked)
         self._next_button.connect('clicked', self._on_next_button_clicked)
-        self._progress_scale.connect(
-            'button-press-event', self._on_progress_scale_event)
-        self._progress_scale.connect(
-            'value-changed', self._on_progress_value_changed)
-        self._progress_scale.connect(
-            'button-release-event', self._on_progress_scale_button_released)
-        self._progress_scale.connect(
-            'change-value', self._on_progress_scale_seek)
-        self._ps_draw = self._progress_scale.connect(
-            'draw', self._on_progress_scale_draw)
-
-        self._seek_timeout = None
-        self._old_progress_scale_value = 0.0
-        self._progress_scale.set_increments(300, 600)
-
-    def _on_progress_scale_seek_finish(self, value):
-        """Prevent stutters when seeking with infinitesimal amounts"""
-        self._seek_timeout = None
-        round_digits = self._progress_scale.get_property('round-digits')
-        if self._old_progress_scale_value != round(value, round_digits):
-            self.on_progress_scale_change_value(self._progress_scale)
-            self._old_progress_scale_value = round(value, round_digits)
-
-        self._player.state = Playback.PLAYING
-        return False
-
-    def _on_progress_scale_seek(self, scale, scroll_type, value):
-        """Smooths out the seeking process
-
-        Called every time progress scale is moved. Only after a seek
-        has been stable for 100ms, play the song from its location.
-        """
-        if self._seek_timeout:
-            GLib.source_remove(self._seek_timeout)
-
-        Gtk.Range.do_change_value(scale, scroll_type, value)
-        if scroll_type == Gtk.ScrollType.JUMP:
-            self._seek_timeout = GLib.timeout_add(
-                100, self._on_progress_scale_seek_finish, value)
-        else:
-            # Scroll with keys, hence no smoothing.
-            self._on_progress_scale_seek_finish(value)
-            self._update_position_callback()
-
-        return True
 
     @log
-    def _on_progress_scale_button_released(self, scale, data):
-        if self._seek_timeout:
-            GLib.source_remove(self._seek_timeout)
-            self._on_progress_scale_seek_finish(
-                self._progress_scale.get_value())
-
-        self._update_position_callback()
-        return False
-
-    def _on_progress_value_changed(self, widget):
-        seconds = int(self._progress_scale.get_value() / 60)
-        self._progress_time_label.set_label(utils.seconds_to_string(seconds))
-        return False
-
-    @log
-    def _on_progress_scale_event(self, scale, data):
-        self._remove_timeout()
-        self._old_progress_scale_value = self._progress_scale.get_value()
-        return False
-
-    def _on_progress_scale_draw(self, cr, data):
-        self._update_timeout()
-        self._progress_scale.disconnect(self._ps_draw)
-        return False
-
-    def _update_timeout(self):
-        """Update the duration for self.timeout & self._seconds_timeout
-
-        Sets the period of self.timeout to a value small enough to make
-        the slider of self._progress_scale move smoothly based on the
-        current song duration and progress_scale length.
-        self._seconds_timeout is always set to a fixed value, short
-        enough to hide irregularities in GLib event timing from the
-        user, for updating the _progress_time_label.
-        """
-        # Do not run until progress_scale has been realized and
-        # gstreamer provides a duration.
-        duration = self._player.duration
-        if (self._progress_scale.get_realized() is False
-                or duration is None):
-            return
-
-        # Update self.timeout.
-        width = self._progress_scale.get_allocated_width()
-        padding = self._progress_scale.get_style_context().get_padding(
-            Gtk.StateFlags.NORMAL)
-        width -= padding.left + padding.right
-
-        timeout_period = min(1000 * duration // width, 1000)
-
-        if self.timeout:
-            GLib.source_remove(self.timeout)
-        self.timeout = GLib.timeout_add(
-            timeout_period, self._update_position_callback)
-
-        # Update self._seconds_timeout.
-        if not self._seconds_timeout:
-            self._seconds_period = 1000
-            self._seconds_timeout = GLib.timeout_add(
-                self._seconds_period, self._update_seconds_callback)
-
-    def _remove_timeout(self):
-        if self.timeout:
-            GLib.source_remove(self.timeout)
-            self.timeout = None
-        if self._seconds_timeout:
-            GLib.source_remove(self._seconds_timeout)
-            self._seconds_timeout = None
-
-    def _progress_scale_zero(self):
-        self._progress_scale.set_value(0)
-        self._on_progress_value_changed(None)
+    def _on_seek_finished(self, klass, time):
+        self._player.state = Playback.PLAYING
 
     @log
     def _on_play_button_clicked(self, button):
@@ -768,39 +655,6 @@ class Player(GObject.GObject):
         self.played_seconds = 0
         self._progress_scale.set_range(0.0, duration * 60)
 
-    @log
-    def _update_position_callback(self):
-        position = self._player.position
-        if position > 0:
-            self._progress_scale.set_value(position * 60)
-        self._update_timeout()
-        return False
-
-    @log
-    def _update_seconds_callback(self):
-        self._on_progress_value_changed(None)
-
-        position = self._player.position
-        if position > 0:
-            self.played_seconds += self._seconds_period / 1000
-            try:
-                percentage = self.played_seconds / self.duration
-                if (not self._lastfm.scrobbled
-                        and percentage > 0.4):
-                    current_media = self.get_current_media()
-                    if current_media:
-                        # FIXME: we should not need to update static
-                        # playlists here but removing it may introduce
-                        # a bug. So, we keep it for the time being.
-                        playlists.update_all_static_playlists()
-                        grilo.bump_play_count(current_media)
-                        grilo.set_last_played(current_media)
-                        self._lastfm.scrobble(current_media, self._time_stamp)
-
-            except Exception as e:
-                logger.warn("Error: %s, %s", e.__class__, e)
-        return True
-
     @log
     def _sync_repeat_image(self):
         icon = None
@@ -816,24 +670,11 @@ class Player(GObject.GObject):
         self._repeat_button_image.set_from_icon_name(icon, Gtk.IconSize.MENU)
         self.emit('repeat-mode-changed')
 
-    @log
-    def on_progress_scale_change_value(self, scroll):
-        seconds = scroll.get_value() / 60
-        self._player.seek(seconds)
-        try:
-            # FIXME mpris
-            self.emit('seeked', seconds * 1000000)
-        except TypeError:
-            # See https://bugzilla.gnome.org/show_bug.cgi?id=733095
-            pass
-
-        return True
-
     # MPRIS
 
     @log
     def Stop(self):
-        self._progress_scale_zero()
+        self._progress_scale._progress_scale_zero()
         self._progress_scale.set_sensitive(False)
         self._play_button.set_image(self._play_image)
         self.stop()
diff --git a/gnomemusic/widgets/Makefile.am b/gnomemusic/widgets/Makefile.am
index 69b259c..29d8ca7 100644
--- a/gnomemusic/widgets/Makefile.am
+++ b/gnomemusic/widgets/Makefile.am
@@ -9,4 +9,5 @@ app_PYTHON = \
        disclistboxwidget.py \
        notificationspopup.py \
        playlistdialog.py \
+       smoothscale.py \
        starhandlerwidget.py
diff --git a/gnomemusic/widgets/smoothscale.py b/gnomemusic/widgets/smoothscale.py
new file mode 100644
index 0000000..6c6a562
--- /dev/null
+++ b/gnomemusic/widgets/smoothscale.py
@@ -0,0 +1,218 @@
+# Copyright (c) 2018 The GNOME Music Developers
+#
+# GNOME Music is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# GNOME Music is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with GNOME Music; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# The GNOME Music authors hereby grant permission for non-GPL compatible
+# GStreamer plugins to be used and distributed together with GStreamer
+# and GNOME Music.  This permission is above and beyond the permissions
+# granted by the GPL license by which GNOME Music is covered.  If you
+# modify this code, you may extend this exception to your version of the
+# code, but you are not obligated to do so.  If you do not wish to do so,
+# delete this exception statement from your version.
+
+import logging
+
+from gettext import gettext as _
+from gi.repository import Gdk, GLib, GObject, Gtk
+
+from gnomemusic import log
+
+logger = logging.getLogger(__name__)
+
+
+class SmoothScale(Gtk.Scale):
+    """Smooth"""
+    __gtype_name__ = 'SmoothScale'
+
+    __gsignals__ = {
+        'seek-finished': (
+            GObject.SignalFlags.RUN_FIRST, None, (float,)
+        ),
+    }
+
+    def __repr__(self):
+        return '<SmoothScale>'
+
+    @log
+    def __init__(self): #, player):
+        super().__init__()
+
+        self._player = None #player
+        self._old_progress_scale_value = 0.0
+        self.set_increments(300, 600)
+        self._seek_timeout = None
+
+        self.timeout = None
+        self._seconds_timeout = 0
+        self._seconds_period = 0
+        self.played_seconds = 0
+
+        self.connect('button-press-event', self._on_progress_scale_event)
+        self.connect('value-changed', self._on_progress_value_changed)
+        self.connect('button-release-event', self._on_progress_scale_button_released)
+        self.connect('change-value', self._on_progress_scale_seek)
+        self._ps_draw = self.connect('draw', self._on_progress_scale_draw)
+
+
+    def _on_progress_scale_seek_finish(self, value):
+        """Prevent stutters when seeking with infinitesimal amounts"""
+        self._seek_timeout = None
+        round_digits = self.get_property('round-digits')
+        if self._old_progress_scale_value != round(value, round_digits):
+            self._on_progress_scale_change_value(self)
+            self._old_progress_scale_value = round(value, round_digits)
+
+        self.emit('seek-finished', value)
+        return False
+
+    def _on_progress_scale_seek(self, scale, scroll_type, value):
+        """Smooths out the seeking process
+
+        Called every time progress scale is moved. Only after a seek
+        has been stable for 100ms, play the song from its location.
+        """
+        if self._seek_timeout:
+            GLib.source_remove(self._seek_timeout)
+
+        Gtk.Range.do_change_value(scale, scroll_type, value)
+        if scroll_type == Gtk.ScrollType.JUMP:
+            self._seek_timeout = GLib.timeout_add(
+                100, self._on_progress_scale_seek_finish, value)
+        else:
+            # Scroll with keys, hence no smoothing.
+            self._on_progress_scale_seek_finish(value)
+            self._update_position_callback()
+
+        return True
+
+    @log
+    def _on_progress_scale_button_released(self, scale, data):
+        if self._seek_timeout:
+            GLib.source_remove(self._seek_timeout)
+            self._on_progress_scale_seek_finish(
+                self.get_value())
+
+        self._update_position_callback()
+        return False
+
+    def _on_progress_value_changed(self, widget):
+        seconds = int(self.get_value() / 60)
+        #self._progress_time_label.set_label(utils.seconds_to_string(seconds))
+        return False
+
+    @log
+    def _on_progress_scale_event(self, scale, data):
+        self._remove_timeout()
+        self._old_progress_scale_value = self.get_value()
+        return False
+
+    def _on_progress_scale_draw(self, cr, data):
+        self._update_timeout()
+        self.disconnect(self._ps_draw)
+        return False
+
+    def _update_timeout(self):
+        """Update the duration for self.timeout & self._seconds_timeout
+
+        Sets the period of self.timeout to a value small enough to make
+        the slider of self._progress_scale move smoothly based on the
+        current song duration and progress_scale length.
+        self._seconds_timeout is always set to a fixed value, short
+        enough to hide irregularities in GLib event timing from the
+        user, for updating the _progress_time_label.
+        """
+        # Do not run until progress_scale has been realized and
+        # gstreamer provides a duration.
+        duration = self._player.duration
+        if (self.get_realized() is False
+                or duration is None):
+            return
+
+        # Update self.timeout.
+        width = self.get_allocated_width()
+        padding = self.get_style_context().get_padding(
+            Gtk.StateFlags.NORMAL)
+        width -= padding.left + padding.right
+
+        timeout_period = min(1000 * duration // width, 1000)
+
+        if self.timeout:
+            GLib.source_remove(self.timeout)
+        self.timeout = GLib.timeout_add(
+            timeout_period, self._update_position_callback)
+
+        # Update self._seconds_timeout.
+        if not self._seconds_timeout:
+            self._seconds_period = 1000
+            self._seconds_timeout = GLib.timeout_add(
+                self._seconds_period, self._update_seconds_callback)
+
+    def _remove_timeout(self):
+        if self.timeout:
+            GLib.source_remove(self.timeout)
+            self.timeout = None
+        if self._seconds_timeout:
+            GLib.source_remove(self._seconds_timeout)
+            self._seconds_timeout = None
+
+    def _progress_scale_zero(self):
+        self.set_value(0)
+        self._on_progress_value_changed(None)
+
+    @log
+    def _on_progress_scale_change_value(self, scroll):
+        seconds = scroll.get_value() / 60
+        self._player.seek(seconds)
+#        try:
+            # FIXME mpris
+            # self.emit('seeked', seconds * 1000000)
+#        except TypeError:
+            # See https://bugzilla.gnome.org/show_bug.cgi?id=733095
+#            pass
+
+        return True
+
+    @log
+    def _update_position_callback(self):
+        position = self._player.position
+        if position > 0:
+            self.set_value(position * 60)
+        self._update_timeout()
+        return False
+
+    @log
+    def _update_seconds_callback(self):
+        self._on_progress_value_changed(None)
+
+        position = self._player.position
+        if position > 0:
+            self.played_seconds += self._seconds_period / 1000
+            try:
+                percentage = self.played_seconds / self.duration
+                if (not self._lastfm.scrobbled
+                        and percentage > 0.4):
+                    current_media = self.get_current_media()
+                    if current_media:
+                        # FIXME: we should not need to update static
+                        # playlists here but removing it may introduce
+                        # a bug. So, we keep it for the time being.
+                        playlists.update_all_static_playlists()
+                        grilo.bump_play_count(current_media)
+                        grilo.set_last_played(current_media)
+                        self._lastfm.scrobble(current_media, self._time_stamp)
+
+            except Exception as e:
+                logger.warn("Error: %s, %s", e.__class__, e)
+        return True
\ No newline at end of file


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]