from typing import Optional, Self, Type, cast from PyQt6.QtCore import ( QBuffer, QByteArray, QCryptographicHash, QDir, QObject, QStandardPaths, QUrl, pyqtSlot, ) from PyQt6.QtMultimedia import ( QAudioOutput, QMediaDevices, QMediaPlayer, QSoundEffect, ) from PyQt6.QtNetwork import ( QNetworkAccessManager, QNetworkDiskCache, QNetworkReply, QNetworkRequest, ) # from PyQt6.QtWidgets import QWidget class SoundOff(QObject): _instance = None def __new__(cls: Type[Self]) -> Self: if cls._instance: return cls._instance cls._instance = super(SoundOff, cls).__new__(cls) return cls._instance def __init__(self) -> None: super().__init__() # # Setup devices # self.virtualDevice = None for output in QMediaDevices.audioOutputs(): if output.id().data().decode("utf-8") == "virt-input": self.virtualDevice = output if output.isDefault(): self.localDevice = output self.alertEffect = QSoundEffect() self.alertEffect.setSource(QUrl("qrc:/beep.wav")) self.alertEffect.setAudioDevice(self.localDevice) self.alertEffect.setVolume(0.25) self.alertEffect.setLoopCount(1) self.localPlayer = QMediaPlayer() self.localPlayer.setObjectName("localPlayer") self.localOutput = QAudioOutput() self.localOutput.setDevice(self.localDevice) self.localPlayer.setAudioOutput(self.localOutput) if self.virtualDevice: self.virtualPlayer = QMediaPlayer() self.virtualPlayer.setObjectName("virtualPlayer") self.virtualOutput = QAudioOutput() self.virtualOutput.setVolume(1.0) self.virtualOutput.setDevice(self.virtualDevice) self.virtualPlayer.setAudioOutput(self.virtualOutput) # # Connections # self.localPlayer.errorOccurred.connect(self.mediaError) self.localPlayer.mediaStatusChanged.connect(self.mediaStatus) self.localPlayer.playbackStateChanged.connect(self.playbackState) if self.virtualDevice: self.virtualPlayer.errorOccurred.connect(self.mediaError) self.virtualPlayer.mediaStatusChanged.connect(self.mediaStatus) self.virtualPlayer.playbackStateChanged.connect(self.playbackState) cacheDir = QDir( QStandardPaths.writableLocation( QStandardPaths.StandardLocation.GenericCacheLocation ) ) cacheDir.mkdir("Troglodite") cacheDir = QDir(cacheDir.path() + QDir.separator() + "Troglodite") netCache = QNetworkDiskCache() netCache.setCacheDirectory(cacheDir.path()) self.nam = QNetworkAccessManager() self.nam.setCache(netCache) self.nam.finished.connect(self.finished) return @pyqtSlot(QMediaPlayer.Error, str) def mediaError(self, error: QMediaPlayer.Error, msg: str) -> None: print(error) print(msg) return @pyqtSlot(QMediaPlayer.MediaStatus) def mediaStatus(self, status: QMediaPlayer.MediaStatus) -> None: # print(f"mediaStatus: {status}") if status == QMediaPlayer.MediaStatus.LoadedMedia: player: Optional[QMediaPlayer] = cast(QMediaPlayer, self.sender()) assert player is not None player.play() return @pyqtSlot(QMediaPlayer.PlaybackState) def playbackState(self, state: QMediaPlayer.PlaybackState) -> None: # print(f"playbackState: {state}") return # # Communications slots # @pyqtSlot() def soundAlert(self) -> None: self.alertEffect.play() return @pyqtSlot(str) def playSound(self, url: str | QUrl) -> None: if isinstance(url, str): url = QUrl(url) if not self.localPlayer.audioOutput(): self.localPlayer.setAudioOutput(self.localOutput) if self.virtualPlayer and not self.virtualPlayer.audioOutput(): self.virtualPlayer.setAudioOutput(self.virtualOutput) if url != self._lastUrl: request = QNetworkRequest(url) self.nam.get(request) self._lastUrl = url return for player in [self.localPlayer, self.virtualPlayer]: if not player: continue player.setPosition(0) if player.mediaStatus() == QMediaPlayer.MediaStatus.LoadedMedia: player.play() return @pyqtSlot() def alert(self) -> None: self.alertEffect.play() return # # Network slots # _storage: dict[QMediaPlayer, QByteArray] = {} _buffer: dict[QMediaPlayer, QBuffer] = {} _lastUrl = QUrl() @pyqtSlot(QNetworkReply) def finished(self, reply: QNetworkReply) -> None: storage = reply.readAll() crypto = QCryptographicHash(QCryptographicHash.Algorithm.Sha256) for player in [self.localPlayer, self.virtualPlayer]: if not player: continue self._storage[player] = QByteArray(storage) crypto.addData(self._storage[player]) print(player, crypto.result().toHex()) crypto.reset() self._buffer[player] = QBuffer(self._storage[player]) url = reply.request().url() player.setSourceDevice(self._buffer[player], url) player.setPosition(0) if player.mediaStatus() == QMediaPlayer.MediaStatus.LoadedMedia: player.play() print("play") return