Bombsquad-Ballistica-Modded.../dist/ba_data/python/baclassic/osmusic.py

168 lines
5.3 KiB
Python
Raw Normal View History

2021-03-29 03:24:13 +05:30
# Released under the MIT License. See LICENSE for details.
#
"""Music playback using OS functionality exposed through the C++ layer."""
from __future__ import annotations
import os
import random
2023-08-13 17:21:49 +05:30
import logging
2021-03-29 03:24:13 +05:30
import threading
2024-05-19 18:25:43 +05:30
from typing import TYPE_CHECKING, override
2021-03-29 03:24:13 +05:30
2023-08-13 17:21:49 +05:30
import babase
from baclassic._music import MusicPlayer
2021-03-29 03:24:13 +05:30
if TYPE_CHECKING:
2022-06-30 00:31:52 +05:30
from typing import Callable, Any
2021-03-29 03:24:13 +05:30
2025-02-09 00:17:58 +05:30
import bauiv1
2021-03-29 03:24:13 +05:30
class OSMusicPlayer(MusicPlayer):
"""Music player that talks to internal C++ layer for functionality.
(internal)"""
def __init__(self) -> None:
super().__init__()
self._want_to_play = False
self._actually_playing = False
@classmethod
def get_valid_music_file_extensions(cls) -> list[str]:
2021-03-29 03:24:13 +05:30
"""Return file extensions for types playable on this device."""
# FIXME: should ask the C++ layer for these; just hard-coding for now.
return ['mp3', 'ogg', 'm4a', 'wav', 'flac', 'mid']
2024-01-27 21:25:16 +05:30
@override
def on_select_entry(
self,
callback: Callable[[Any], None],
current_entry: Any,
selection_target_name: str,
2025-02-09 00:17:58 +05:30
) -> bauiv1.MainWindow:
2021-03-29 03:24:13 +05:30
# pylint: disable=cyclic-import
2023-08-13 17:21:49 +05:30
from bauiv1lib.soundtrack.entrytypeselect import (
SoundtrackEntryTypeSelectWindow,
)
return SoundtrackEntryTypeSelectWindow(
callback, current_entry, selection_target_name
)
2021-03-29 03:24:13 +05:30
2024-01-27 21:25:16 +05:30
@override
2021-03-29 03:24:13 +05:30
def on_set_volume(self, volume: float) -> None:
2023-08-13 17:21:49 +05:30
babase.music_player_set_volume(volume)
2021-03-29 03:24:13 +05:30
2024-01-27 21:25:16 +05:30
@override
2021-03-29 03:24:13 +05:30
def on_play(self, entry: Any) -> None:
2023-08-13 17:21:49 +05:30
assert babase.app.classic is not None
music = babase.app.classic.music
2021-03-29 03:24:13 +05:30
entry_type = music.get_soundtrack_entry_type(entry)
name = music.get_soundtrack_entry_name(entry)
assert name is not None
if entry_type == 'musicFile':
self._want_to_play = self._actually_playing = True
2023-08-13 17:21:49 +05:30
babase.music_player_play(name)
2021-03-29 03:24:13 +05:30
elif entry_type == 'musicFolder':
# Launch a thread to scan this folder and give us a random
# valid file within it.
self._want_to_play = True
self._actually_playing = False
_PickFolderSongThread(
name,
self.get_valid_music_file_extensions(),
self._on_play_folder_cb,
).start()
def _on_play_folder_cb(
self, result: str | list[str], error: str | None = None
) -> None:
2021-03-29 03:24:13 +05:30
if error is not None:
2023-08-13 17:21:49 +05:30
rstr = babase.Lstr(
resource='internal.errorPlayingMusicText'
).evaluate()
2021-03-29 03:24:13 +05:30
if isinstance(result, str):
err_str = (
rstr.replace('${MUSIC}', os.path.basename(result))
+ '; '
+ str(error)
)
2021-03-29 03:24:13 +05:30
else:
err_str = (
rstr.replace('${MUSIC}', '<multiple>') + '; ' + str(error)
)
2023-08-13 17:21:49 +05:30
babase.screenmessage(err_str, color=(1, 0, 0))
2021-03-29 03:24:13 +05:30
return
# There's a chance a stop could have been issued before our thread
# returned. If that's the case, don't play.
if not self._want_to_play:
print('_on_play_folder_cb called with _want_to_play False')
else:
self._actually_playing = True
2023-08-13 17:21:49 +05:30
babase.music_player_play(result)
2021-03-29 03:24:13 +05:30
2024-01-27 21:25:16 +05:30
@override
2021-03-29 03:24:13 +05:30
def on_stop(self) -> None:
self._want_to_play = False
self._actually_playing = False
2023-08-13 17:21:49 +05:30
babase.music_player_stop()
2021-03-29 03:24:13 +05:30
2024-01-27 21:25:16 +05:30
@override
2021-03-29 03:24:13 +05:30
def on_app_shutdown(self) -> None:
2023-08-13 17:21:49 +05:30
babase.music_player_shutdown()
2021-03-29 03:24:13 +05:30
class _PickFolderSongThread(threading.Thread):
def __init__(
self,
path: str,
valid_extensions: list[str],
callback: Callable[[str | list[str], str | None], None],
):
2021-03-29 03:24:13 +05:30
super().__init__()
self._valid_extensions = valid_extensions
self._callback = callback
self._path = path
2024-01-27 21:25:16 +05:30
@override
2021-03-29 03:24:13 +05:30
def run(self) -> None:
2023-08-13 17:21:49 +05:30
do_log_error = True
2021-03-29 03:24:13 +05:30
try:
2023-08-13 17:21:49 +05:30
babase.set_thread_name('BA_PickFolderSongThread')
all_files: list[str] = []
2021-03-29 03:24:13 +05:30
valid_extensions = ['.' + x for x in self._valid_extensions]
for root, _subdirs, filenames in os.walk(self._path):
for fname in filenames:
if any(
fname.lower().endswith(ext) for ext in valid_extensions
):
all_files.insert(
random.randrange(len(all_files) + 1),
root + '/' + fname,
)
2021-03-29 03:24:13 +05:30
if not all_files:
2023-08-13 17:21:49 +05:30
do_log_error = False
2021-03-29 03:24:13 +05:30
raise RuntimeError(
2023-08-13 17:21:49 +05:30
babase.Lstr(
resource='internal.noMusicFilesInFolderText'
).evaluate()
)
2023-08-13 17:21:49 +05:30
babase.pushcall(
babase.Call(self._callback, all_files, None),
from_other_thread=True,
)
2021-03-29 03:24:13 +05:30
except Exception as exc:
2023-08-13 17:21:49 +05:30
if do_log_error:
logging.exception('Error in _PickFolderSongThread')
2021-03-29 03:24:13 +05:30
try:
err_str = str(exc)
except Exception:
err_str = '<ENCERR4523>'
2023-08-13 17:21:49 +05:30
babase.pushcall(
babase.Call(self._callback, self._path, err_str),
from_other_thread=True,
)