class VideoPlaybackDevice(Filter): playbackStarted = Signal() playbackPaused = Signal() sequenceOpened = Signal(str, QDateTime, QDateTime, list) currentTimestampChanged = Signal(QDateTime) def __init__(self, environment): super().__init__(False, False, environment) self.video_out = OutputPort(False, "video", environment) self.audio_out = OutputPort(False, "audio", environment) self.addStaticPort(self.video_out) self.addStaticPort(self.audio_out) self.filename = self.propertyCollection().defineProperty( "filename", "", "avi file name") def newDuration(self, newDuration): logger.debug("newDuration %s", newDuration) self.sequenceOpened.emit(self.filename, QDateTime.fromMSecsSinceEpoch(0), QDateTime.fromMSecsSinceEpoch(newDuration), ["video"]) def currentMediaChanged(self, media): logger.debug("currentMediaChanged videoAv=%s audioAv=%s", self.player.isVideoAvailable(), self.player.isAudioAvailable()) def _openVideo(self): logger.debug("entering _openVideo") self.player = QMediaPlayer(None, QMediaPlayer.VideoSurface) self.videoOutput = DummyVideoSurface(self.player) #self.videoOutput = QVideoWidget() #self.videoOutput.show() self.player.setVideoOutput(self.videoOutput) #self.player.setMuted(True) self.player.durationChanged.connect(self.newDuration) self.player.currentMediaChanged.connect(self.currentMediaChanged) self.player.setMedia(QUrl.fromLocalFile(self.filename)) logger.debug("leaving _openVideo; videoAv=%s audioAv=%s", self.player.isVideoAvailable(), self.player.isAudioAvailable()) def _closeVideo(self): try: del self.player del self.playlist except: pass def onStart(self): ctrlSrv = Services.getService("PlaybackControl") ctrlSrv.setupConnections(self) self.playbackPaused.emit() if self.filename != "": self._openVideo() def onStop(self): ctrlSrv = Services.getService("PlaybackControl") ctrlSrv.removeConnections(self) self._closeVideo() @Slot() def startPlayback(self): self.player.play() self.playbackStarted.emit() logger.debug("leaving startPlayback; videoAv=%s audioAv=%s", self.player.isVideoAvailable(), self.player.isAudioAvailable()) @Slot() def pausePlayback(self): self.player.pause() self.playbackPaused.emit() def newDataEvent(self): t = time.monotonic() if self.lastSendTime is not None: if t - self.lastSendTime < self.timeout_ms * 1e-3: # we are still earlier than the requested framerate return self.lastSendTime = t self.counter += 1 c = "Sample %d" % self.counter s = DataSample(c.encode("utf8"), "text/utf8", int(time.time() / DataSample.TIMESTAMP_RES)) logging.getLogger(__name__).info("transmit: %s", c) self.beforeTransmit(s) self.outPort.transmit(s) self.afterTransmit() def stepForward(self): pass def stepBackward(self): pass def seekBeginning(self): pass def seekEnd(self): pass def seekTime(self, timestamp): pass def setSequence(self, filename): self.filename def setTimeFactor(self, factor): pass
class Sound: """Loads a sound from the specified URL. Supports whatever audio formats that PySide2 supports (depending on locally-installed codecs). No error is raised if the file isn't found or is of an unsupported format. Sounds are tracked :param url: the URL to load the sound from, can be a local file """ def __init__(self, url): # type: (str) -> None self.__url = url # Only used for debugging # Tell the app not to quit while this sound is loading, since it is plausible that a user is using sounds # without using a frame or any timers: TheApp.add_tracked(self) self.__player = QMediaPlayer(TheApp, flags=QMediaPlayer.LowLatency) self.__player.setAudioRole(QAudio.GameRole) self.__player.mediaStatusChanged.connect(self.__on_status_changed) self.__player.error.connect(self.__on_status_changed) req = request(url) content = QMediaContent(req) self.__player.setMedia(content) self.__sound_loaded = False self.__play_requested = False def __on_status_changed(self, _): # type: (QMediaPlayer.MediaStatus) -> None """Checks if the sound is loaded. If the sound has loaded without error, and the user has already told it to start playing, this will start the sound playing. If there was any failure in loading the sound, this is recorded, and the sound will never be able to be played. TheApp will also be notified that it can close if this sound is all it is waiting for. This event is also triggered by other status changes. The only other one that matters is the EndOfMedia status. In this case, TheApp is also told that it can close. :param _: (unused) media status object """ error = self.__player.error() status = self.__player.mediaStatus() if status < QMediaPlayer.LoadedMedia: return if error == QMediaPlayer.NoError and QMediaPlayer.LoadedMedia <= status < QMediaPlayer.InvalidMedia: # Check if the media is actually an audio file that is playable if self.__player.isAudioAvailable(): self.__sound_loaded = True if self.__play_requested and status != QMediaPlayer.EndOfMedia: # Play and don't do anything else now self.play() return else: self.__sound_loaded = False self.__play_requested = False TheApp.remove_tracked(self) def play(self): """Starts playing a sound, or restarts playing it at the point it was paused.""" if self.__sound_loaded: self.__player.play() TheApp.add_tracked(self) self.__play_requested = True def pause(self): """Stops the playing of the sound. Playing can be restarted at the stopped point with :meth:`play`.""" if self.__sound_loaded: self.__player.pause() TheApp.remove_tracked(self) self.__play_requested = False def rewind(self): """Stops playing the sound, makes it so the next :meth:`play` will start playing the sound at the beginning.""" if self.__sound_loaded: self.__player.stop() TheApp.remove_tracked(self) self.__play_requested = False def set_volume(self, volume): # type: (float) -> None """Changes the volume for the sound to be the given level on a 0 (silent) – 1.0 (maximum) scale. Default is 1. :param volume: the volume to set """ assert 0.0 <= volume <= 1.0, "volume must be given in range 0-1 inclusive" self.__player.setVolume(int(100 * volume))