rind: Add seek support

I put a smaller progress bar into the header area that users can use for
seeking or checking the current position. I also add two labels to show
time played and time remaining in the current track.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
Anna Schumaker 2019-03-20 16:57:03 -04:00
parent 5885228fd1
commit a9f48534e5
3 changed files with 116 additions and 6 deletions

View File

@ -2,6 +2,11 @@
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkAdjustment" id="progress_adjustment">
<property name="upper">100</property>
<property name="step_increment">5</property>
<property name="page_increment">10</property>
</object>
<object class="GtkApplicationWindow" id="window">
<property name="visible">True</property>
<property name="can_focus">False</property>
@ -12,6 +17,7 @@
<object class="GtkHeaderBar" id="header">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<property name="show_close_button">True</property>
<child>
<object class="GtkButton" id="play_button">
@ -140,6 +146,48 @@
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="duration">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_right">10</property>
<property name="label" translatable="yes">0:00</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkScale" id="progress_scale">
<property name="width_request">125</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">progress_adjustment</property>
<property name="show_fill_level">True</property>
<property name="fill_level">100</property>
<property name="digits">-1</property>
<property name="draw_value">False</property>
<property name="value_pos">left</property>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="position">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">0:00</property>
<property name="xalign">1</property>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">6</property>
</packing>
</child>
</object>
</child>
<child>

View File

@ -1,15 +1,20 @@
# Copyright 2019 (c) Anna Schumaker.
from . import gtk
import curds
import datetime
import gi
import sys
gi.require_version("Gst", "1.0")
from gi.repository import Gst, GLib
Gst.init(sys.argv)
Adjustment = gtk.Builder.get_object("progress_adjustment")
Duration = gtk.Builder.get_object("duration")
NextButton = gtk.Builder.get_object("next_button")
PauseButton = gtk.Builder.get_object("pause_button")
PlayButton = gtk.Builder.get_object("play_button")
Position = gtk.Builder.get_object("position")
ProgScale = gtk.Builder.get_object("progress_scale")
Subtitle = gtk.Builder.get_object("subtitle")
Title = gtk.Builder.get_object("title")
@ -24,6 +29,9 @@ class EmmentalAudio:
PauseButton.connect("clicked", self.pause)
PlayButton.connect( "clicked", self.play)
NextButton.connect( "clicked", self.next)
ProgScale.connect("change-value", self.seek)
self.timeout = GLib.timeout_add(100, self.update_progress)
def duration(self):
(res, cur) = self.playbin.query_duration(Gst.Format.TIME)
@ -60,11 +68,26 @@ class EmmentalAudio:
def position(self):
(res, cur) = self.playbin.query_position(Gst.Format.TIME)
return cur if res == True else 0
return cur / Gst.SECOND if res == True else 0
def progress(self):
pos = self.position()
dur = self.duration()
return (pos / dur) if dur > 0 else 0
def seek(self, range=None, scroll=None, value=0):
(res, dur) = self.playbin.query_duration(Gst.Format.TIME)
if res == True:
pos = (dur * value) / 100.0
self.playbin.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH, pos)
def update_progress(self):
(p_m, p_s) = divmod(int(self.position()), 60)
(d_m, d_s) = divmod(int(self.duration()) - int(self.position()), 60)
Position.set_text(datetime.time(0, p_m, p_s).strftime("%M:%S"))
Duration.set_text(datetime.time(0, d_m, d_s).strftime("-%M:%S"))
Adjustment.set_value(self.progress() * 100)
return GLib.SOURCE_CONTINUE
Audio = EmmentalAudio()

View File

@ -3,6 +3,7 @@ from . import gtk
from . import gst
import curds
import os
import time
import unittest
from gi.repository import Gst, Gtk, GLib
@ -24,7 +25,7 @@ class TestGst(unittest.TestCase):
curds.playlist.library.library_thread.stop()
def main_loop(self):
events = GLib.main_context_default().iteration()
events = GLib.main_context_default().iteration(may_block=True)
while events == True:
events = GLib.main_context_default().iteration(may_block=False)
@ -32,12 +33,20 @@ class TestGst(unittest.TestCase):
audio = gst.EmmentalAudio()
self.assertIsInstance(audio.playbin, Gst.Element)
self.assertIsInstance(audio.bus, Gst.Bus)
self.assertIsInstance(gst.Adjustment, Gtk.Adjustment)
self.assertIsInstance(gst.Duration, Gtk.Label)
self.assertIsInstance(gst.NextButton, Gtk.Button)
self.assertIsInstance(gst.PauseButton, Gtk.Button)
self.assertIsInstance(gst.PlayButton, Gtk.Button)
self.assertIsInstance(gst.Position, Gtk.Label)
self.assertIsInstance(gst.ProgScale, Gtk.Scale)
self.assertFalse(gst.PauseButton.is_visible())
self.assertTrue( gst.PlayButton.is_visible())
self.assertEqual(gst.Adjustment.get_value(), 0.0)
self.assertEqual(gst.Position.get_text(), "00:00")
self.assertEqual(gst.Duration.get_text(), "-00:00")
audio.load(None)
self.assertFalse(gst.PauseButton.is_visible())
@ -51,6 +60,11 @@ class TestGst(unittest.TestCase):
self.assertFalse(gst.PauseButton.is_visible())
self.assertTrue( gst.PlayButton.is_visible())
audio.seek(value=20)
self.assertEqual(gst.Adjustment.get_value(), 0.0)
self.assertEqual(gst.Position.get_text(), "00:00")
self.assertEqual(gst.Duration.get_text(), "-00:00")
self.assertEqual(audio.position(), 0)
self.assertEqual(audio.duration(), 0)
self.assertEqual(audio.progress(), 0)
@ -73,15 +87,24 @@ class TestGst(unittest.TestCase):
self.main_loop()
self.assertEqual(self.state, Gst.State.PAUSED)
self.assertEqual(audio.position(), 0)
self.assertEqual(audio.duration(), 10.0)
self.assertEqual(gst.Title.get_text(), track['title'])
self.assertEqual(gst.Subtitle.get_text(), "by " + track["artist"])
self.assertEqual(audio.progress(), 0)
self.assertEqual(gtk.Builder.get_object("title").get_text(), track['title'])
self.assertEqual(gtk.Builder.get_object("subtitle").get_text(), "by " + track["artist"])
self.assertEqual(audio.position(), 0)
self.assertEqual(gst.Position.get_text(), "00:00")
while self.state != Gst.State.PLAYING:
time.sleep(0.1)
self.main_loop()
self.assertEqual(audio.duration(), 10.0)
self.assertEqual(gst.Duration.get_text(), "-00:10")
GLib.source_remove(audio.timeout)
gst.Position.set_text("00:00")
gst.Duration.set_text("-00:00")
def test_gst_controls(self):
audio = gst.EmmentalAudio()
gst.NextButton.clicked()
@ -104,6 +127,18 @@ class TestGst(unittest.TestCase):
self.assertFalse(gst.PauseButton.is_visible())
self.assertTrue( gst.PlayButton.is_visible())
audio.seek(value=50.0)
time.sleep(0.1)
self.main_loop()
self.assertEqual(audio.duration(), 3.0)
self.assertEqual(audio.position(), 1.5)
self.assertEqual(gst.Position.get_text(), "00:01")
self.assertEqual(gst.Duration.get_text(), "-00:02")
audio.seek(value=0.0)
time.sleep(0.1)
self.main_loop()
gst.PlayButton.clicked()
while self.state != Gst.State.PLAYING:
self.main_loop()
@ -113,3 +148,7 @@ class TestGst(unittest.TestCase):
gst.PauseButton.clicked()
while self.state != Gst.State.PAUSED:
self.main_loop()
GLib.source_remove(audio.timeout)
gst.Position.set_text("00:00")
gst.Duration.set_text("-00:00")