383 lines
16 KiB
Python
383 lines
16 KiB
Python
# Copyright 2022 (c) Anna Schumaker.
|
|
"""Tests our GObject audio player wrapping a GStreamer Playbin element."""
|
|
import io
|
|
import pathlib
|
|
import unittest
|
|
import unittest.mock
|
|
import emmental.audio
|
|
import tests.util
|
|
from gi.repository import GObject
|
|
from gi.repository import GLib
|
|
from gi.repository import Gst
|
|
|
|
|
|
class TestAudio(unittest.TestCase):
|
|
"""Our audio player test case."""
|
|
|
|
def setUp(self):
|
|
"""Set up common variables."""
|
|
self.player = emmental.audio.Player()
|
|
|
|
def tearDown(self):
|
|
"""Clean up the playbin."""
|
|
self.player.shutdown()
|
|
|
|
def main_loop(self) -> None:
|
|
"""Run a GLib main loop."""
|
|
while GLib.main_context_default().iteration():
|
|
pass
|
|
|
|
def test_constants(self):
|
|
"""Test audio player constants."""
|
|
self.assertEqual(emmental.audio.UPDATE_INTERVAL, 100)
|
|
self.assertEqual(emmental.audio.SEEK_FLAGS,
|
|
Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT)
|
|
|
|
def test_player(self):
|
|
"""Test that the audio player was set up correctly."""
|
|
self.assertIsInstance(self.player, GObject.GObject)
|
|
self.assertEqual(self.player.get_state(), Gst.State.READY)
|
|
|
|
self.assertIsNone(self.player.file)
|
|
self.assertFalse(self.player.have_track)
|
|
self.assertEqual(self.player.status, "Stopped")
|
|
|
|
def test_playbin(self):
|
|
"""Test that the playbin was configured correctly."""
|
|
self.assertIsInstance(self.player._playbin, Gst.Element)
|
|
self.assertIsInstance(self.player._playbin.get_property("video-sink"),
|
|
Gst.Element)
|
|
self.assertIsNone(self.player._timeout, None)
|
|
|
|
self.assertRegex(self.player._playbin.name, r"playbin\d+")
|
|
self.assertRegex(self.player._playbin.get_property("video-sink").name,
|
|
r"fakesink\d+")
|
|
|
|
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
|
def test_filter(self, mock_stdout: io.StringIO):
|
|
"""Test the filter element added to the playbin."""
|
|
self.assertIsInstance(self.player._filter,
|
|
emmental.audio.filter.Filter)
|
|
self.assertEqual(self.player._playbin.get_property("audio-filter"),
|
|
self.player._filter)
|
|
|
|
self.assertFalse(self.player.bg_enabled)
|
|
self.assertEqual(self.player.bg_volume, 0.5)
|
|
|
|
self.player.bg_enabled = True
|
|
self.player.bg_volume = 0.75
|
|
self.assertTrue(self.player._filter.bg_enabled)
|
|
self.assertEqual(self.player._filter.bg_volume, 0.75)
|
|
|
|
self.player.bg_enabled = False
|
|
self.player.bg_volume = 0.5
|
|
self.assertFalse(self.player._filter.bg_enabled)
|
|
self.assertEqual(self.player.bg_volume, 0.5)
|
|
|
|
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
|
def test_eos(self, mock_stdout: io.StringIO):
|
|
"""Test handling an EOS message."""
|
|
self.player.file = tests.util.TRACK_OGG
|
|
self.player.duration = 10
|
|
self.player.position = 8
|
|
self.player.playtime = 6
|
|
self.player.savedtime = 4
|
|
self.player.almost_done = True
|
|
self.player.artwork = pathlib.Path("/a/b/c.jpg")
|
|
|
|
eos = Gst.Message.new_eos(self.player._playbin)
|
|
self.player._playbin.get_bus().post(eos)
|
|
self.main_loop()
|
|
|
|
self.assertRegex(mock_stdout.getvalue(), "audio: end of stream")
|
|
|
|
for prop in ["artist", "album-artist", "album", "title"]:
|
|
self.assertEqual(self.player.get_property(prop), "")
|
|
for prop in ["album-disc-number", "track-number",
|
|
"position", "duration", "playtime", "savedtime"]:
|
|
self.assertEqual(self.player.get_property(prop), 0)
|
|
self.assertIsNone(self.player.artwork)
|
|
|
|
self.assertEqual(self.player.get_state(), Gst.State.READY)
|
|
self.assertEqual(self.player.status, "Stopped")
|
|
self.assertFalse(self.player.have_track)
|
|
self.assertFalse(self.player.almost_done)
|
|
self.assertIsNone(self.player.file)
|
|
|
|
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
|
def test_almost_done(self, mock_stdout: io.StringIO):
|
|
"""Test notifying that the current track is almost done."""
|
|
self.assertFalse(self.player.almost_done)
|
|
|
|
self.player.almost_done = True
|
|
self.player.file = tests.util.TRACK_OGG
|
|
self.player.pause()
|
|
self.main_loop()
|
|
self.assertFalse(self.player.almost_done)
|
|
|
|
self.player.duration = (5 * Gst.SECOND) / Gst.USECOND
|
|
with unittest.mock.patch.object(self.player._playbin,
|
|
"query_position") as query_position:
|
|
query_position.return_value = (True, 2 * Gst.SECOND)
|
|
self.player._Player__update_position()
|
|
self.assertFalse(self.player.almost_done)
|
|
self.assertNotRegex(mock_stdout.getvalue(),
|
|
"audio: about to finish")
|
|
|
|
query_position.return_value = (True, 3 * Gst.SECOND)
|
|
self.player._Player__update_position()
|
|
self.assertTrue(self.player.almost_done)
|
|
self.assertRegex(mock_stdout.getvalue(), "audio: about to finish")
|
|
|
|
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
|
def test_file(self, mock_stdout: io.StringIO):
|
|
"""Test that the file property works as expected."""
|
|
self.player.file = tests.util.TRACK_OGG
|
|
self.assertEqual(self.player._playbin.get_property("uri"),
|
|
tests.util.TRACK_OGG.as_uri())
|
|
self.assertEqual(self.player.title, "")
|
|
self.assertEqual(self.player.album, "")
|
|
|
|
started = unittest.mock.Mock()
|
|
self.player.connect("file-loaded", started)
|
|
self.player.pause()
|
|
self.main_loop()
|
|
started.assert_called_with(self.player, tests.util.TRACK_OGG)
|
|
self.assertTrue(self.player.have_track)
|
|
|
|
self.assertEqual(mock_stdout.getvalue(),
|
|
f"audio: loading {tests.util.TRACK_OGG.as_uri()}\n"
|
|
"audio: file loaded\n"
|
|
"audio: state changed to 'paused'\n")
|
|
|
|
self.player.playtime = 6
|
|
self.player.savedtime = 4
|
|
self.player.emit("file-loaded", tests.util.TRACK_OGG)
|
|
for prop in ["artist", "album-artist", "album", "title"]:
|
|
self.assertEqual(self.player.get_property(prop), "")
|
|
for prop in ["album-disc-number", "track-number",
|
|
"playtime", "savedtime"]:
|
|
self.assertEqual(self.player.get_property(prop), 0)
|
|
|
|
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
|
def test_play(self, mock_stdout: io.StringIO):
|
|
"""Test that the play() function works as expected."""
|
|
self.player.play()
|
|
self.main_loop()
|
|
self.assertFalse(self.player.playing)
|
|
self.assertEqual(self.player.status, "Stopped")
|
|
|
|
self.player.file = tests.util.TRACK_OGG
|
|
self.player.play()
|
|
self.main_loop()
|
|
self.assertTrue(self.player.playing)
|
|
self.assertEqual(self.player.status, "Playing")
|
|
self.assertEqual(self.player.get_state(), Gst.State.PLAYING)
|
|
self.assertRegex(mock_stdout.getvalue(),
|
|
"audio: state changed to 'playing'$")
|
|
|
|
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
|
def test_pause(self, mock_stdout: io.StringIO):
|
|
"""Test that the pause() function works as expected."""
|
|
self.player.pause()
|
|
self.main_loop()
|
|
self.assertEqual(self.player.status, "Stopped")
|
|
|
|
self.player.playing = True
|
|
self.player.file = tests.util.TRACK_OGG
|
|
self.player.pause()
|
|
self.main_loop()
|
|
self.assertFalse(self.player.playing)
|
|
self.assertEqual(self.player.status, "Paused")
|
|
self.assertEqual(self.player.get_state(), Gst.State.PAUSED)
|
|
self.assertRegex(mock_stdout.getvalue(),
|
|
"audio: state changed to 'paused'$")
|
|
|
|
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
|
def test_play_pause(self, mock_stdout: io.StringIO):
|
|
"""Test that the play_pause() function works as expected."""
|
|
self.player.play_pause()
|
|
self.main_loop()
|
|
self.assertFalse(self.player.playing)
|
|
self.assertEqual(self.player.status, "Stopped")
|
|
|
|
self.player.file = tests.util.TRACK_OGG
|
|
self.player.play_pause()
|
|
self.main_loop()
|
|
self.assertTrue(self.player.playing)
|
|
self.assertEqual(self.player.status, "Playing")
|
|
self.assertEqual(self.player.get_state(), Gst.State.PLAYING)
|
|
self.assertRegex(mock_stdout.getvalue(),
|
|
"audio: state changed to 'playing'$")
|
|
|
|
self.player.play_pause()
|
|
self.main_loop()
|
|
self.assertFalse(self.player.playing)
|
|
self.assertEqual(self.player.status, "Paused")
|
|
self.assertEqual(self.player.get_state(), Gst.State.PAUSED)
|
|
self.assertRegex(mock_stdout.getvalue(),
|
|
"audio: state changed to 'playing'\n"
|
|
"audio: state changed to 'paused'\n$")
|
|
|
|
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
|
def test_stop(self, mock_stdout: io.StringIO):
|
|
"""Test that the stop() function works as expected."""
|
|
self.player.file = tests.util.TRACK_OGG
|
|
self.player.play()
|
|
self.main_loop()
|
|
|
|
self.player.stop()
|
|
self.main_loop()
|
|
self.assertFalse(self.player.playing)
|
|
self.assertEqual(self.player.status, "Stopped")
|
|
self.assertEqual(self.player.get_state(), Gst.State.READY)
|
|
self.assertRegex(mock_stdout.getvalue(),
|
|
"audio: state changed to 'playing'\n"
|
|
"audio: state changed to 'stopped'\n$")
|
|
|
|
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
|
def test_tags(self, mock_stdout: io.StringIO):
|
|
"""Test that the tag properties work as expected."""
|
|
for prop in ["artist", "album", "album-artist", "title"]:
|
|
self.assertEqual(self.player.get_property(prop), "")
|
|
for prop in ["album-disc-number", "track-number"]:
|
|
self.assertEqual(self.player.get_property(prop), 0)
|
|
|
|
self.player.file = tests.util.TRACK_OGG
|
|
self.player.pause()
|
|
self.main_loop()
|
|
self.assertEqual(self.player.artist, "Test Artist")
|
|
self.assertEqual(self.player.album, "Test Album")
|
|
self.assertEqual(self.player.album_artist, "Test Album Artist")
|
|
self.assertEqual(self.player.album_disc_number, 1)
|
|
self.assertEqual(self.player.title, "Test Title")
|
|
self.assertEqual(self.player.track_number, 1)
|
|
|
|
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
|
def test_duration(self, mock_stdout: io.StringIO):
|
|
"""Test the duration property."""
|
|
self.assertEqual(self.player.duration, 0)
|
|
self.player.file = tests.util.TRACK_OGG
|
|
self.player.pause()
|
|
self.main_loop()
|
|
self.assertEqual(self.player.duration, 10 * Gst.SECOND / Gst.USECOND)
|
|
|
|
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
|
def test_position(self, mock_stdout: io.StringIO):
|
|
"""Test the position property."""
|
|
self.assertEqual(self.player.position, 0)
|
|
|
|
query_position = unittest.mock.Mock(return_value=(True, Gst.SECOND))
|
|
self.player._playbin.query_position = query_position
|
|
self.player._Player__update_position()
|
|
self.assertEqual(self.player.position, Gst.SECOND / Gst.USECOND)
|
|
|
|
seek_simple = unittest.mock.Mock()
|
|
self.player._playbin.seek_simple = seek_simple
|
|
self.player.seek(5 * Gst.SECOND / Gst.USECOND)
|
|
seek_simple.assert_called_with(Gst.Format.TIME,
|
|
emmental.audio.SEEK_FLAGS,
|
|
5 * Gst.SECOND)
|
|
|
|
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
|
def test_playtime(self, mock_stdout: io.StringIO):
|
|
"""Test the play time property."""
|
|
self.assertEqual(self.player.playtime, 0.0)
|
|
self.assertEqual(self.player.savedtime, 0.0)
|
|
self.assertEqual(self.player._Player__get_current_playtime(), 0.0)
|
|
|
|
self.player.file = tests.util.TRACK_OGG
|
|
self.player.play()
|
|
self.main_loop()
|
|
self.assertIsNotNone(self.player._playbin.clock)
|
|
|
|
base_time = unittest.mock.PropertyMock(return_value=Gst.SECOND)
|
|
type(self.player._playbin).base_time = base_time
|
|
get_time = unittest.mock.Mock(return_value=3 * Gst.SECOND)
|
|
self.player._playbin.clock.get_time = get_time
|
|
|
|
self.assertEqual(self.player._Player__get_current_playtime(), 2.0)
|
|
self.player._Player__update_position()
|
|
self.assertEqual(self.player.playtime, 2)
|
|
|
|
with unittest.mock.patch.object(self.player._playbin, "seek_simple"):
|
|
self.player.seek(5)
|
|
self.assertEqual(self.player.savedtime, 2)
|
|
|
|
self.player._Player__update_position()
|
|
self.assertEqual(self.player.playtime, 4)
|
|
|
|
def test_volume(self):
|
|
"""Test that the volume property works as expected."""
|
|
self.assertEqual(self.player.volume, 1.0)
|
|
self.assertEqual(self.player._playbin.get_property("volume"), 1.0)
|
|
self.player.volume = 0.5
|
|
self.assertEqual(self.player.volume, 0.5)
|
|
self.assertEqual(self.player._playbin.get_property("volume"), 0.5)
|
|
|
|
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
|
def test_replaygain(self, mock_stdout: io.StringIO):
|
|
"""Test that ReplayGain functions work as expected."""
|
|
self.assertEqual(self.player._filter.rg_mode, "disabled")
|
|
self.assertEqual(self.player.get_replaygain(), (False, None))
|
|
|
|
self.player.set_replaygain(True, "album")
|
|
self.assertEqual(self.player._filter.rg_mode, "album")
|
|
self.assertEqual(self.player.get_replaygain(), (True, "album"))
|
|
self.assertRegex(mock_stdout.getvalue(),
|
|
r"audio: setting ReplayGain mode to 'album'")
|
|
|
|
self.player.set_replaygain(False, "track")
|
|
self.assertEqual(self.player._filter.rg_mode, "disabled")
|
|
self.assertEqual(self.player.get_replaygain(), (False, None))
|
|
self.assertRegex(mock_stdout.getvalue(),
|
|
r"audio: setting ReplayGain mode to 'disabled'")
|
|
|
|
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
|
def test_artwork(self, mock_stdout: io.StringIO):
|
|
"""Test that we handle album artwork."""
|
|
self.assertIsNone(self.player.artwork)
|
|
|
|
with unittest.mock.patch.object(pathlib.Path, "is_file",
|
|
return_value=False):
|
|
self.player.file = tests.util.TRACK_OGG
|
|
self.player.pause()
|
|
self.main_loop()
|
|
self.assertIsNone(self.player.artwork)
|
|
|
|
self.player.stop()
|
|
self.player.pause()
|
|
self.main_loop()
|
|
self.assertEqual(self.player.artwork, tests.util.COVER_JPG)
|
|
|
|
buffer = Gst.Buffer.new_wrapped_bytes(tests.util.COVER_JPG_BYTES)
|
|
taglist = Gst.TagList.new_empty()
|
|
taglist.add_value(Gst.TagMergeMode.APPEND, "image",
|
|
Gst.Sample.new(buffer))
|
|
tag = Gst.Message.new_tag(self.player._playbin, taglist)
|
|
self.player._playbin.get_bus().post(tag)
|
|
self.main_loop()
|
|
|
|
expected = emmental.tmpdir.cover_jpg()
|
|
self.assertEqual(self.player.artwork,
|
|
emmental.tmpdir.cover_jpg())
|
|
self.assertTrue(expected.is_file())
|
|
|
|
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
|
def test_pause_on_load(self, mock_stdout: io.StringIO):
|
|
"""Test pausing the next time a file is loaded."""
|
|
self.assertFalse(self.player.pause_on_load)
|
|
|
|
self.player.pause_on_load = True
|
|
self.player.file = tests.util.TRACK_OGG
|
|
self.player.play()
|
|
|
|
self.main_loop()
|
|
self.assertFalse(self.player.playing)
|
|
self.assertFalse(self.player.pause_on_load)
|
|
|
|
def test_shutdown(self):
|
|
"""Test that the shutdown function works as expected."""
|
|
self.player.shutdown()
|
|
self.assertEqual(self.player.get_state(), Gst.State.NULL)
|