Repository: kivy/kivy

Issue:
sound.length always returns 0
The `sound.length` function is implemented to always return 0 on both `gstreamer` and `pygame` incarnations of the audio library. Yet it's documented to return the length of the sound...



Discussion:

Resolving patch:
diff --git a/kivy/core/audio/audio_gstreamer.py b/kivy/core/audio/audio_gstreamer.py
--- a/kivy/core/audio/audio_gstreamer.py
+++ b/kivy/core/audio/audio_gstreamer.py
@@ -104,4 +104,20 @@ def _set_volume(self, volume):
             self._data.set_property('volume', volume)
         return super(SoundGstreamer, self)._set_volume(volume)
 
+    def _get_length(self):
+        if self._data is not None:
+            if self._data.get_state()[1] != gst.STATE_PLAYING:
+                volume_before = self._data.get_property('volume')
+                self._data.set_property('volume', 0)
+                self._data.set_state(gst.STATE_PLAYING)
+                try:
+                    self._data.get_state()
+                    return self._data.query_duration(gst.Format(gst.FORMAT_TIME))[0] / 1000000000.
+                finally:
+                    self._data.set_state(gst.STATE_NULL)
+                    self._data.set_property('volume', volume_before)
+            else:
+                return self._data.query_duration(gst.Format(gst.FORMAT_TIME))[0] / 1000000000.
+        return super(SoundGstreamer, self)._get_length()
+
 SoundLoader.register(SoundGstreamer)
diff --git a/kivy/core/audio/audio_pygame.py b/kivy/core/audio/audio_pygame.py
--- a/kivy/core/audio/audio_pygame.py
+++ b/kivy/core/audio/audio_pygame.py
@@ -88,4 +88,9 @@ def _set_volume(self, volume):
             self._data.set_volume(volume)
         return super(SoundPygame, self)._set_volume(volume)
 
+    def _get_length(self):
+        if self._data is not None:
+            return self._data.get_length()
+        return super(SoundPygame, self)._get_length()
+
 SoundLoader.register(SoundPygame)


Test patch:
diff --git a/kivy/tests/sample1.ogg b/kivy/tests/sample1.ogg
new file mode 100644
Binary files /dev/null and b/kivy/tests/sample1.ogg differ
diff --git a/kivy/tests/test_audio.py b/kivy/tests/test_audio.py
new file mode 100644
--- /dev/null
+++ b/kivy/tests/test_audio.py
@@ -0,0 +1,65 @@
+'''
+Audio tests
+===========
+'''
+
+import unittest
+import os
+
+SAMPLE_FILE = os.path.join(os.path.dirname(__file__), 'sample1.ogg')
+SAMPLE_LENGTH = 1.402
+SAMPLE_LENGTH_MIN = SAMPLE_LENGTH * 0.99
+SAMPLE_LENGTH_MAX = SAMPLE_LENGTH * 1.01
+
+class AudioTestCase(unittest.TestCase):
+
+    def setUp(self):
+        from kivy.core import audio
+        self.make_sound = audio.SoundLoader.load
+
+    def get_sound(self):
+        import os
+        assert os.path.exists(SAMPLE_FILE)
+        return self.make_sound(filename=SAMPLE_FILE)
+
+    def test_length_simple(self):
+        sound = self.get_sound()
+        volume = sound.volume = 0.75
+        length = sound.length
+        assert SAMPLE_LENGTH_MIN <= length <= SAMPLE_LENGTH_MAX
+        # ensure that the gstreamer play/stop doesn't mess up the volume
+        assert volume == sound.volume
+
+    def test_length_playing(self):
+        import time
+        sound = self.get_sound()
+        sound.play()
+        try:
+            time.sleep(0.1)
+            length = sound.length
+            assert SAMPLE_LENGTH_MIN <= length <= SAMPLE_LENGTH_MAX
+        finally:
+            sound.stop()
+
+    def test_length_stopped(self):
+        import time
+        sound = self.get_sound()
+        sound.play()
+        try:
+            time.sleep(0.1)
+        finally:
+            sound.stop()
+        length = sound.length
+        assert SAMPLE_LENGTH_MIN <= length <= SAMPLE_LENGTH_MAX
+
+class AudioGstreamerTestCase(AudioTestCase):
+    def setUp(self):
+        from kivy.core.audio import audio_gstreamer
+        self.make_sound = audio_gstreamer.SoundGstreamer
+
+class AudioPygameTestCase(AudioTestCase):
+    def setUp(self):
+        from kivy.core.audio import audio_pygame
+        self.make_sound = audio_pygame.SoundPygame
+
+