Beispiel #1
0
def test_get_routes():
    osc = OSCThreadServer(encoding='utf8')
    osc.listen(default=True)

    values = []

    @osc.address(u'/test_route')
    def dummy(*val):
        pass

    @osc.address(u'/_oscpy/routes/answer')
    def cb(*routes):
        values.extend(routes)

    send_message(b'/_oscpy/routes', [osc.getaddress()[1]],
                 *osc.getaddress(),
                 encoding='utf8',
                 encoding_errors='strict')

    timeout = time() + 2
    while not values:
        if time() > timeout:
            raise OSError('timeout while waiting for success message.')
        sleep(10e-9)

    assert u'/test_route' in values
Beispiel #2
0
def test_getaddress():
    osc = OSCThreadServer()
    sock = osc.listen()
    assert osc.getaddress(sock)[0] == '127.0.0.1'
    with pytest.raises(RuntimeError):
        osc.getaddress()

    sock2 = osc.listen(default=True)
    assert osc.getaddress(sock2)[0] == '127.0.0.1'
    osc.stop(sock)
Beispiel #3
0
def test_default_handler():
    results = []

    def test(address, *values):
        results.append((address, values))

    osc = OSCThreadServer(default_handler=test)
    osc.listen(default=True)

    @osc.address(b'/passthrough')
    def passthrough(*values):
        pass

    osc.send_bundle((
        (b'/test', []),
        (b'/passthrough', []),
        (b'/test/2', [1, 2, 3]),
    ), *osc.getaddress())

    timeout = time() + 2
    while len(results) < 2:
        if time() > timeout:
            raise OSError('timeout while waiting for success message.')
        sleep(10e-9)

    expected = (
        (b'/test', tuple()),
        (b'/test/2', (1, 2, 3)),
    )

    for e, r in zip(expected, results):
        assert e == r
Beispiel #4
0
def test_get_sender():
    osc = OSCThreadServer(encoding='utf8')
    osc.listen(default=True)

    values = []

    @osc.address(u'/test_route')
    def callback(*val):
        values.append(osc.get_sender())

    with pytest.raises(RuntimeError,
                       match='get_sender\(\) not called from a callback'):
        osc.get_sender()

    send_message(b'/test_route', [osc.getaddress()[1]],
                 *osc.getaddress(),
                 encoding='utf8')

    timeout = time() + 2
    while not values:
        if time() > timeout:
            raise OSError('timeout while waiting for success message.')
        sleep(10e-9)
Beispiel #5
0
def test_get_version():
    osc = OSCThreadServer(encoding='utf8')
    osc.listen(default=True)

    values = []

    @osc.address(u'/_oscpy/version/answer')
    def cb(val):
        print(val)
        values.append(val)

    send_message(b'/_oscpy/version', [osc.getaddress()[1]],
                 *osc.getaddress(),
                 encoding='utf8',
                 encoding_errors='strict')

    timeout = time() + 2
    while not values:
        if time() > timeout:
            raise OSError('timeout while waiting for success message.')
        sleep(10e-9)

    assert __version__ in values
Beispiel #6
0
def test_bind_default():
    osc = OSCThreadServer()
    osc.listen(default=True)
    port = osc.getaddress()[1]
    cont = []

    def success(*values):
        cont.append(True)

    osc.bind(b'/success', success)

    send_message(b'/success', [b'test', 1, 1.12345], 'localhost', port)

    timeout = time() + 5
    while not cont:
        if time() > timeout:
            raise OSError('timeout while waiting for success message.')
Beispiel #7
0
def test_validate_message_address_disabled():
    osc = OSCThreadServer(validate_message_address=False)
    osc.listen(default=True)

    received = []

    @osc.address(b'malformed')
    def malformed(*val):
        received.append(val[0])

    send_message(b'malformed', [b'message'], *osc.getaddress())

    timeout = time() + 2
    while not received:
        if time() > timeout:
            raise OSError('timeout while waiting for success message.')
        sleep(10e-9)
Beispiel #8
0
def test_bind_address():
    osc = OSCThreadServer()
    osc.listen(default=True)
    result = []

    @osc.address(b'/test')
    def success(*args):
        result.append(True)

    timeout = time() + 1

    send_message(b'/test', [], *osc.getaddress())

    while len(result) < 1:
        if time() > timeout:
            raise OSError('timeout while waiting for success message.')
        sleep(10e-9)

    assert True in result
Beispiel #9
0
def test_encoding_receive():
    osc = OSCThreadServer(encoding='utf8')
    osc.listen(default=True)

    values = []

    @osc.address(u'/encoded')
    def encoded(*val):
        for v in val:
            assert not isinstance(v, bytes)
        values.append(val)

    send_message(b'/encoded', [b'hello world', u'ééééé ààààà'.encode('utf8')],
                 *osc.getaddress())

    timeout = time() + 2
    while not values:
        if time() > timeout:
            raise OSError('timeout while waiting for success message.')
        sleep(10e-9)
Beispiel #10
0
def test_answer():
    cont = []

    osc_1 = OSCThreadServer()
    osc_1.listen(default=True)

    @osc_1.address(b'/ping')
    def ping(*values):
        if True in values:
            osc_1.answer(b'/zap', [True], port=osc_3.getaddress()[1])
        else:
            osc_1.answer(bundle=[(b'/pong', [])])

    osc_2 = OSCThreadServer()
    osc_2.listen(default=True)

    @osc_2.address(b'/pong')
    def pong(*values):
        osc_2.answer(b'/ping', [True])

    osc_3 = OSCThreadServer()
    osc_3.listen(default=True)

    @osc_3.address(b'/zap')
    def zap(*values):
        if True in values:
            cont.append(True)

    osc_2.send_message(b'/ping', [], *osc_1.getaddress())

    with pytest.raises(RuntimeError) as e_info:  # noqa
        osc_1.answer(b'/bing', [])

    timeout = time() + 2
    while not cont:
        if time() > timeout:
            raise OSError('timeout while waiting for success message.')
        sleep(10e-9)
Beispiel #11
0
def test_bind_address_class():
    osc = OSCThreadServer()
    osc.listen(default=True)

    @ServerClass
    class Test(object):
        def __init__(self):
            self.result = []

        @osc.address_method(b'/test')
        def success(self, *args):
            self.result.append(True)

    timeout = time() + 1

    test = Test()
    send_message(b'/test', [], *osc.getaddress())

    while len(test.result) < 1:
        if time() > timeout:
            raise OSError('timeout while waiting for success message.')
        sleep(10e-9)

    assert True in test.result
Beispiel #12
0
    """Count calls."""
    global received
    received += 1


for family in 'unix', 'inet':
    osc = OSCThreadServer()
    print(f"family: {family}")
    if family == 'unix':
        address, port = '/tmp/test_sock', 0
        if os.path.exists(address):
            os.unlink(address)
        sock = SOCK = osc.listen(address=address, family='unix')
    else:
        SOCK = sock = osc.listen()
        address, port = osc.getaddress(sock)

    osc.bind(b'/count', count, sock=sock)
    for i, pattern in enumerate(patterns):
        for safer in (False, True):
            timeout = time() + DURATION
            sent = 0
            received = 0

            while time() < timeout:
                send_message(b'/count',
                             pattern,
                             address,
                             port,
                             sock=SOCK,
                             safer=safer)
Beispiel #13
0
def test_advanced_matching():
    osc = OSCThreadServer(advanced_matching=True)
    osc.listen(default=True)
    port = osc.getaddress()[1]
    result = {}

    def save_result(f):
        name = f.__name__

        def wrapped(*args):
            r = result.get(name, [])
            r.append(args)
            result[name] = r
            return f(*args)

        return wrapped

    @osc.address(b'/?')
    @save_result
    def singlechar(*values):
        pass

    @osc.address(b'/??')
    @save_result
    def twochars(*values):
        pass

    @osc.address(b'/prefix*')
    @save_result
    def prefix(*values):
        pass

    @osc.address(b'/*suffix')
    @save_result
    def suffix(*values):
        pass

    @osc.address(b'/[abcd]')
    @save_result
    def somechars(*values):
        pass

    @osc.address(b'/{string1,string2}')
    @save_result
    def somestrings(*values):
        pass

    @osc.address(b'/part1/part2')
    @save_result
    def parts(*values):
        pass

    @osc.address(b'/part1/*/part3')
    @save_result
    def parts_star(*values):
        pass

    @osc.address(b'/part1/part2/?')
    @save_result
    def parts_prefix(*values):
        pass

    @osc.address(b'/part1/[abcd]/part3')
    @save_result
    def parts_somechars(*values):
        pass

    @osc.address(b'/part1/[c-f]/part3')
    @save_result
    def parts_somecharsrange(*values):
        pass

    @osc.address(b'/part1/[!abcd]/part3')
    @save_result
    def parts_notsomechars(*values):
        pass

    @osc.address(b'/part1/[!c-f]/part3')
    @save_result
    def parts_notsomecharsrange(*values):
        pass

    @osc.address(b'/part1/{string1,string2}/part3')
    @save_result
    def parts_somestrings(*values):
        pass

    @osc.address(b'/part1/part2/{string1,string2}')
    @save_result
    def parts_somestrings2(*values):
        pass

    @osc.address(b'/part1/part2/prefix-{string1,string2}')
    @save_result
    def parts_somestrings3(*values):
        pass

    send_bundle((
        (b'/a', [1]),
        (b'/b', [2]),
        (b'/z', [3]),
        (b'/1', [3]),
        (b'/?', [4]),
        (b'/ab', [5]),
        (b'/bb', [6]),
        (b'/z?', [7]),
        (b'/??', [8]),
        (b'/?*', [9]),
        (b'/prefixab', [10]),
        (b'/prefixbb', [11]),
        (b'/prefixz?', [12]),
        (b'/prefix??', [13]),
        (b'/prefix?*', [14]),
        (b'/absuffix', [15]),
        (b'/bbsuffix', [16]),
        (b'/z?suffix', [17]),
        (b'/??suffix', [18]),
        (b'/?*suffix', [19]),
        (b'/string1', [20]),
        (b'/string2', [21]),
        (b'/string1aa', [22]),
        (b'/string1b', [23]),
        (b'/string1?', [24]),
        (b'/astring1?', [25]),
        (b'/part1', [26]),
        (b'/part1/part', [27]),
        (b'/part1/part2', [28]),
        (b'/part1/part3/part2', [29]),
        (b'/part1/part2/part3', [30]),
        (b'/part1/part?/part2', [31]),
        (b'/part1', [32]),
        (b'/part1/a/part', [33]),
        (b'/part1/b/part2', [34]),
        (b'/part1/c/part3/part2', [35]),
        (b'/part1/d/part2/part3', [36]),
        (b'/part1/e/part?/part2', [37]),
        (b'/part1/test/part2', [38]),
        (b'/part1/a/part2', [39]),
        (b'/part1/b/part2', [40]),
        (b'/part1/c/part2/part2', [41]),
        (b'/part1/d/part2/part3', [42]),
        (b'/part1/0/part2', [43]),
        (b'/part1/string1/part', [45]),
        (b'/part1/string2/part3', [46]),
        (b'/part1/part2/string1', [47]),
        (b'/part1/part2/string2', [48]),
        (b'/part1/part2/prefix-string1', [49]),
        (b'/part1/part2/sprefix-tring2', [50]),
    ), 'localhost', port)

    expected = {
        'singlechar': [(1, ), (2, ), (3, ), (3, ), (4, )],
        'twochars': [(5, ), (6, ), (7, ), (8, ), (9, )],
        'prefix': [(10, ), (11, ), (12, ), (13, ), (14, )],
        'suffix': [(15, ), (16, ), (17, ), (18, ), (19, )],
        'somechars': [(1, ), (2, )],
        'somestrings': [(20, ), (21, )],
        'parts': [(28, )],
        'parts_star': [(30, ), (46, )],
        'parts_somestrings': [(46, )],
        'parts_somestrings2': [(47, ), (48, )],
        'parts_somestrings3': [(49, )]
    }

    timeout = time() + 5
    while result != expected:
        if time() > timeout:
            print("expected: {}\n result: {}\n".format(expected, result))
            raise OSError('timeout while waiting for expected result.')
        sleep(10e-9)
Beispiel #14
0
class ServerSong():
    def __init__(self, app, pos_callback, state_callback, port):
        from oscpy.server import OSCThreadServer
        self.app = app
        self.pos_callback = pos_callback
        self.state_callback = state_callback
        self.state = 'stop'
        self.length = 30
        self.is_complete = False
        self.song_object = None
        self.ready = False

        self.osc = OSCThreadServer()
        self.osc.listen(port=port, default=True)
        self.osc.bind(b'/pos', self._get_pos)
        self.osc.bind(b'/set_state', self.set_state)
        self.osc.bind(b'/set_length', self.set_length)
        self.osc.bind(b'/set_complete', self.set_complete)
        self.osc.bind(b'/playing', self.playing)
        self.osc.bind(b'/update_playlist', self.update_playlist)
        self.osc.bind(b'/ready', self.ready_callback)

    def ready_callback(self):
        self.ready = True
        Logger.debug("ACTIVITY: Service is ready.")

    def playing(self, id, pos):
        Logger.debug('ACTIVITY: Playing.')
        self.state = 'play'
        self.last_pos = pos
        song = self.app.playlist.get_track(id=id)
        play_button = self.app.play_button
        if self.song_object != song:
            play_button.load_song(song, playing=True)
        play_button.update_track_current(current=pos)
        if play_button.event:
            play_button.event.cancel()
        play_button.event = Clock.schedule_interval(
            play_button.update_track_current, 1)

    def set_state(self, value):
        Logger.debug('ACTIVITY: State %s', value)
        self.state = value.decode()

    def set_length(self, value):
        Logger.debug('ACTIVITY: Length %s', value)
        self.length = value

    def set_complete(self, value):
        Logger.debug('ACTIVITY: Song is_complete=%s', value)
        self.is_complete = value

    def set_volume(self, value):
        Logger.debug('ACTIVITY -> Service: Set volume %s', value)
        self.osc.send_message(b'/set_volume', [value], *self.server_address)

    def getaddress(self):
        return self.osc.getaddress()

    def load(self, song):
        Logger.debug('ACTIVITY -> SERVER: /load')
        self.osc.send_message(b'/load', [song.id], *self.server_address)

    def unload(self):
        Logger.debug('ACTIVITY -> SERVER: /unload')
        self.osc.send_message(b'/unload', [], *self.server_address)

    def play(self, seek, volume):
        Logger.debug('ACTIVITY -> SERVER: /play pos %s vol %s', seek, volume)
        self.osc.send_message(b'/play', [seek, volume], *self.server_address)

    def play_new_playlist(self):
        Logger.debug('ACTIVITY -> SERVER: /play_new_playlist')
        self.osc.send_message(b'/play_new_playlist', [], *self.server_address)

    def load_play(self, song, volume):
        Logger.debug('ACTIVITY -> SERVER: /load_play %s vol %s', song.id,
                     volume)
        self.osc.send_message(b'/load_play', [song.id, volume],
                              *self.server_address)

    def stop(self):
        Logger.debug('ACTIVITY -> SERVER: /stop')
        self.osc.send_message(b'/stop', [], *self.server_address)

    def seek(self, position):
        Logger.debug('ACTIVITY -> SERVER: /seek %s', position)
        self.osc.send_message(b'/seek', [position], *self.server_address)

    def save_pos(self, callback):
        Logger.debug('ACTIVITY -> SERVER: /get_pos')
        self.pos_callback = callback
        self.osc.send_message(b'/get_pos', [], *self.server_address)

    def get_pos(self, callback):
        Logger.debug('ACTIVITY -> SERVER: get pos and call %s', callback)
        self.pos_callback = callback
        self.osc.send_message(b'/get_pos', [], *self.server_address)

    def _get_pos(self, value):
        Logger.debug('ACTIVITY: pos received %s', value)
        self.pos_callback(value)

    def update_playlist(self):
        self.app.playlist = self.app.db.get_playlist()
        Logger.debug('ACTIVITY: Updated playlist.')
Beispiel #15
0
class OSCSever:
    def __init__(self, activity_server_address, port):
        self.song = SoundAndroidPlayer(self.on_complete)
        self.osc = OSCThreadServer()
        self.osc.listen(port=port, default=True)
        self.activity_server_address = activity_server_address
        self.osc.bind(b'/get_pos', self.get_pos)
        self.osc.bind(b'/load', self.load)
        self.osc.bind(b'/play', self.play)
        self.osc.bind(b'/load_play', self.load_play)
        self.osc.bind(b'/seek', self.seek)
        self.osc.bind(b'/play_new_playlist', self.play_next)
        self.osc.bind(b'/set_volume', self.set_volume)
        self.osc.bind(b'/stop', self.pause)
        self.osc.bind(b'/unload', self.unload)

        self.api = API()
        self.db = Database()
        user = self.db.get_user()
        self.genres = user['genres']
        self.artists = user['artists']
        self.volume = user['volume']
        self.songs_path = user['songs_path']
        self.playlist = self.db.get_playlist()
        self.waiting_for_load = False
        self.seek_pos = 0
        self.downloading = None
        self.waiting_for_download = False
        self.downloads = []

    def check_pos(self, *args):
        if self.song.state == "play" and self.song.length - self.song.get_pos(
        ) < 20:
            next_song = self.playlist.preview_next()
            if next_song is None or next_song.preview_file is None:
                if next_song is None:
                    self.playlist = self.get_new_playlist()
                    next_song = self.playlist.preview_next()
                Logger.debug('SERVICE: Preloading next song.')
                self.download_song(next_song)

    def thread_download_song(self, song):
        self.downloads.append(song.id)
        try:
            res = self.api.download_preview(song)
        except Exception as e:
            Logger.error("SERVICE: Download failed. Reason: %s", e)
        else:
            song.preview_file = save_song(self.songs_path, song, res)
            self.db.update_track(song, 'preview_file', song.preview_file)
            Logger.debug('SERVICE: Downloading song finished.')
        self.downloads.remove(song.id)

    def download_song(self, song):
        if song.id not in self.downloads and song.preview_file is None:
            Logger.debug('SERVICE: Downloading %s.', song.id)
            t = threading.Thread(target=self.thread_download_song,
                                 args=(song, ))
            t.daemon = True
            t.start()
        else:
            Logger.debug(
                'SERVICE: Skipped downloading %s. Already in progress.',
                song.id)

    def get_new_playlist(self):
        Logger.debug('SERVICE: getting new playlist.')
        req = self.api.get_recommendations(
            self.genres,
            self.artists,
            song_type='preview',
        )
        playlist = Playlist(req)
        self.osc.send_message(b'/update_playlist', [],
                              *self.activity_server_address)
        # clean up playlist songs
        favorites = self.db.get_favorites()
        for song in self.playlist.tracks:
            if (song.preview_file and song not in favorites
                    and song != self.song.song_object):
                Logger.debug("Service: Removed %s", song.id)
                os.remove(song.preview_file)
        return playlist

    def get_pos(self, *values):
        pos = self.song.get_pos() if self.song and self.song.is_prepared else 0
        Logger.debug('SERVICE -> ACTIVITY: /pos %s', pos)
        self.osc.answer(b'/pos', [pos])

    def getaddress(self):
        return self.osc.getaddress()

    def load(self, id):
        self.first_load = not getattr(self.song, "id", 0)
        self.song.id = id
        self.song.is_prepared = False
        Logger.debug('SERVICE: Loading %d.', id)
        song = self.db.get_track(id)
        if song.preview_file is None and self.downloading != song.id:
            Logger.debug('SERVICE: %d is not downloaded.', id)
            self.download_song(song)
        if song.id in self.downloads:
            Logger.debug('SERVICE: %d is downloading. Returning.', id)
            self.waiting_for_download = song.id
            return
        Logger.debug('SERVICE: %d file is available.', id)
        self.waiting_for_download = None
        if not self.first_load:
            self.song.reset()
        self.song.load(song.preview_file)
        self.song.song_object = song
        self.db.update_current_track(song)
        self.playlist = self.db.get_playlist()
        self.song.is_prepared = True
        self.update_notification()
        Logger.debug('SERVICE: Song loaded.')

    def load_play(self, id, volume=None):
        Logger.debug('SERVICE: Loading and playing %d.', id)
        self.pause()
        self.load(id)
        self.play(0, volume if volume is not None else self.volume)

    def play(self, seek, volume):
        if not self.song.is_prepared:
            song_id = getattr(self.song, "id", None)
            Logger.debug('SERVICE: %s is not prepared.',
                         song_id if song_id else "Song")
            if not self.waiting_for_download:
                self.load(self.playlist.current_track.id)
            else:
                self.waiting_for_load = True
                self.seek_pos = seek
                self.volume = volume
                return
        else:
            self.waiting_for_load = False
            self.song.is_prepared = True
        self.song.play()
        self.song.seek(seek)
        self.song.volume = volume
        pos = self.song.get_pos()
        Logger.debug('SERVICE -> ACTIVITY: /playing %s.', pos)
        values = [self.song.song_object.id, pos]
        self.osc.send_message(b'/playing', values,
                              *self.activity_server_address)
        Logger.debug('SERVICE: Playing %d.', self.song.song_object.id)
        self.update_notification()

    def stop(self, *values):
        Logger.debug('SERVICE: stopping %d.', self.song.song_object.id)
        self.waiting_for_load = False
        if self.song.is_prepared and self.song.state == 'play':
            self.song.is_prepared = False
            self.song.stop()
        self.update_notification()

    def pause(self, *values):
        Logger.debug('SERVICE: pausing Song.')
        self.waiting_for_load = False
        if self.song.is_prepared and self.song.state == 'play':
            self.song.pause()
        self.update_notification()

    def seek(self, value):
        Logger.debug('SERVICE: seeking %s.', value)
        if self.song.is_prepared:
            self.song.seek(value)

    def set_volume(self, value):
        Logger.debug('SERVICE: setting song volume %s.', value)
        self.volume = value
        if self.song.is_prepared:
            self.song.volume = value

    def on_complete(self, *values):
        Logger.debug('SERVICE -> ACTIVITY: /set_complete')
        self.osc.send_message(b'/set_complete', [True],
                              *self.activity_server_address)
        self.play_next()

    def play_next(self):
        Logger.debug('SERVICE: Playing next.')
        self.stop()
        if self.playlist.is_last:
            self.playlist = self.get_new_playlist()
        song = self.playlist.next()
        self.db.update_playlist(self.playlist)
        self.load_play(song.id)

    def play_previous(self):
        Logger.debug('SERVICE: Playing previous.')
        if not self.playlist.is_first:
            self.stop()
            self.load_play(self.playlist.previous().id)

    def on_state(self, instance, value):
        Logger.debug('SERVICE -> ACTIVITY: /set_state %s', value)
        self.osc.send_message(b'/set_state', [value.encode()],
                              *self.activity_server_address)
        if self.song.is_prepared and self.song.length - self.song.get_pos(
        ) < 0.2:
            self.on_complete()

    def unload(self, *values):
        self.song.unload()

    def update_notification(self):
        notification = self.create_notification()
        notification_manager = context.getSystemService(
            Context.NOTIFICATION_SERVICE)
        notification_manager.notify(1, notification)

    def create_notification(self):
        song = getattr(self.song, "song_object", None)
        if api_version >= 26:
            builder = NotificationCompatBuilder(context, "gtplayer")
        else:
            builder = NotificationCompatBuilder(context)
        (builder.setContentTitle(song.name if song else "GTPlayer").
         setContentText(song.artist if song else "GTPlayer").setContentIntent(
             controller.getSessionActivity()).setDeleteIntent(
                 MediaButtonReceiver.buildMediaButtonPendingIntent(
                     context, PlaybackStateCompat.ACTION_STOP)).setVisibility(
                         NotificationCompat.VISIBILITY_PUBLIC).setSmallIcon(
                             icon))
        style = MediaStyle().setShowActionsInCompactView(0)
        builder.setStyle(style)

        if song is not None and not self.playlist.is_first:
            previous_intent = MediaButtonReceiver.buildMediaButtonPendingIntent(
                context, PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)
            action = NotificationCompatAction(RDrawable.ic_media_previous,
                                              "Previous", previous_intent)
            builder.addAction(action)

        if self.song.state == "play":
            intent = MediaButtonReceiver.buildMediaButtonPendingIntent(
                context, PlaybackStateCompat.ACTION_PAUSE)
            action = NotificationCompatAction(RDrawable.ic_media_pause,
                                              "Pause", intent)
        else:
            intent = MediaButtonReceiver.buildMediaButtonPendingIntent(
                context, PlaybackStateCompat.ACTION_PLAY)
            action = NotificationCompatAction(RDrawable.ic_media_play, "Play",
                                              intent)
        builder.addAction(action)

        coverart = None
        if song is not None:
            next_intent = MediaButtonReceiver.buildMediaButtonPendingIntent(
                context, PlaybackStateCompat.ACTION_SKIP_TO_NEXT)
            action = NotificationCompatAction(RDrawable.ic_media_next, "Next",
                                              next_intent)
            builder.addAction(action)

            path = os.path.join(images_path, f"{song.id}.png")
            if os.path.isfile(path):
                coverart = path

        coverart = coverart if coverart is not None else "images/empty_coverart.png"
        builder.setLargeIcon(BitmapFactory.decodeFile(coverart))

        return builder.build()