From 8c03601bd2e6261d0905ff43c51d8e2f5c9bd457 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Thu, 25 Apr 2019 08:48:19 -0500 Subject: [PATCH 1/7] Change lable to 'pause' in the play button when something is playing --- src/controller/buffers.py | 13 +++++++++++++ src/presenters/player.py | 12 ++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/controller/buffers.py b/src/controller/buffers.py index 0ede6e8..fa9db03 100644 --- a/src/controller/buffers.py +++ b/src/controller/buffers.py @@ -632,9 +632,12 @@ class audioBuffer(feedBuffer): def connect_events(self): widgetUtils.connect_event(self.tab.play, widgetUtils.BUTTON_PRESSED, self.play_audio) widgetUtils.connect_event(self.tab.play_all, widgetUtils.BUTTON_PRESSED, self.play_all) + pub.subscribe(self.change_label, "playback-changed") super(audioBuffer, self).connect_events() def play_audio(self, *args, **kwargs): + if player.player.stream != None: + return player.player.pause() selected = self.tab.list.get_selected() if selected == -1: selected = 0 @@ -778,6 +781,16 @@ class audioBuffer(feedBuffer): url = "https://vk.com/audio{user_id}_{post_id}".format(user_id=post["owner_id"], post_id=post["id"]) webbrowser.open_new_tab(url) + def change_label(self, stopped): + if hasattr(self.tab, "play"): + if stopped == False: + self.tab.play.SetLabel(_("P&ause")) + else: + self.tab.play.SetLabel(_("P&lay")) + + def __del__(self): + pub.unsubscribe(self.change_label, "playback-changed") + class audioAlbum(audioBuffer): """ this buffer was supposed to be used with audio albums but is deprecated as VK removed its audio support for third party apps.""" diff --git a/src/presenters/player.py b/src/presenters/player.py index 6b366a4..e9691b6 100644 --- a/src/presenters/player.py +++ b/src/presenters/player.py @@ -55,6 +55,18 @@ class audioPlayer(object): pub.subscribe(self.play_previous, "play-previous") pub.subscribe(self.seek, "seek") + # Stopped has a special function here, hence the decorator + # when stopped will be set to True, it will send a pubsub event to inform other parts of the application about the status change. + # this is useful for changing labels between play and pause, and so on, in buttons. + @property + def stopped(self): + return self._stopped + + @stopped.setter + def stopped(self, value): + self._stopped = value + pub.sendMessage("playback-changed", stopped=value) + def play(self, object, set_info=True, fresh=False): """ Play an URl Stream. @object dict: typically an audio object as returned by VK, with a "url" component which must be a valid URL to a media file. From a9032602bfdc60b9b3986997806dfde707931959 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Thu, 25 Apr 2019 08:56:12 -0500 Subject: [PATCH 2/7] Added play and pause functionality in the audios displayer --- changelog.md | 12 ++++++++++++ src/interactors/postDisplayer.py | 9 +++++++++ src/presenters/displayPosts/audio.py | 4 +++- 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 8e968bb..2fec32f 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,18 @@ ### New additions +### bugfixes + +### Changes + +* the audio player module has received some improvements: + - When there is something being played, the label of the "play" button, located in all audio buffers, will change automatically to "pause". When pressed, it will pause the song instead of starting the playback again since the beginning. + - The last change will work also in the dialog for displaying audio information. Now it's possible to play and pause an audio from such dialog. + +## changes in version 0.20 (25.04.2019) + +### New additions + * For users with multiple soundcards, there is a new tab in the preferences dialogue of Socializer, called sound. From there, you can define which soundcard will be used for input and output. [#25,](https://code.manuelcortez.net/manuelcortez/socializer/issues/25) * the audio player can seek positions in the currently playing track. You can use the menu items (located in the main menu) or use the new available keystrokes dedicated to the actions. Seeking will be made in 5 second intervals. * Alt+Shift+Left arrow: Seek 5 seconds backwards. diff --git a/src/interactors/postDisplayer.py b/src/interactors/postDisplayer.py index 8e86252..86089d6 100644 --- a/src/interactors/postDisplayer.py +++ b/src/interactors/postDisplayer.py @@ -171,6 +171,13 @@ class displayAudioInteractor(base.baseInteractor): getattr(self.view, control).Append(i) getattr(self.view, control).SetSelection(0) + def change_label(self, stopped): + + if stopped == False: + self.view.play.SetLabel(_("P&ause")) + else: + self.view.play.SetLabel(_("P&lay")) + def install(self, *args, **kwargs): super(displayAudioInteractor, self).install(*args, **kwargs) widgetUtils.connect_event(self.view.list, widgetUtils.LISTBOX_CHANGED, self.on_change) @@ -180,11 +187,13 @@ class displayAudioInteractor(base.baseInteractor): widgetUtils.connect_event(self.view.remove, widgetUtils.BUTTON_PRESSED, self.on_remove_from_library) pub.subscribe(self.set, self.modulename+"_set") pub.subscribe(self.add_items, self.modulename+"_add_items") + pub.subscribe(self.change_label, "playback-changed") def uninstall(self): super(displayAudioInteractor, self).uninstall() pub.unsubscribe(self.set, self.modulename+"_set") pub.unsubscribe(self.add_items, self.modulename+"_add_items") + pub.unsubscribe(self.change_label, "playback-changed") def on_change(self, *args, **kwargs): post = self.view.get_audio() diff --git a/src/presenters/displayPosts/audio.py b/src/presenters/displayPosts/audio.py index 52cbd78..9fda65e 100644 --- a/src/presenters/displayPosts/audio.py +++ b/src/presenters/displayPosts/audio.py @@ -3,7 +3,7 @@ import logging from sessionmanager import utils from pubsub import pub from mysc.thread_utils import call_threaded -from presenters import base +from presenters import base, player log = logging.getLogger(__file__) @@ -82,6 +82,8 @@ class displayAudioPresenter(base.basePresenter): def play(self, audio_index): post = self.post[audio_index] + if player.player.stream != None: + return player.player.pause() pub.sendMessage("play", object=post) def load_audios(self): From 6bd0c10ef2407193b6865ed069002edf0f20adf7 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Thu, 25 Apr 2019 09:17:48 -0500 Subject: [PATCH 3/7] Removed some old keystrokes no longer needed --- changelog.md | 4 ++++ src/controller/buffers.py | 24 +++++++++++++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/changelog.md b/changelog.md index 2fec32f..4f2327e 100644 --- a/changelog.md +++ b/changelog.md @@ -11,6 +11,10 @@ * the audio player module has received some improvements: - When there is something being played, the label of the "play" button, located in all audio buffers, will change automatically to "pause". When pressed, it will pause the song instead of starting the playback again since the beginning. - The last change will work also in the dialog for displaying audio information. Now it's possible to play and pause an audio from such dialog. +* In audio buffers, you will play the focused audio when pressing enter in the item, instead of opening the audio information dialog. +* Removed some old keystrokes in socializer buffers due to better alternatives. The reason for this change is that currently you don't need to be focusing an item in a buffer for being able to use the alternative keystrokes, which makes those a better implementation. + - Control+Enter and control+Shift+Enter: superseeded by Control+P for playing (and pausing) the currently focused audio. + - F5 and F6: superseeded by Alt+down and up arrows for modifying volume. ## changes in version 0.20 (25.04.2019) diff --git a/src/controller/buffers.py b/src/controller/buffers.py index fa9db03..d80d7cc 100644 --- a/src/controller/buffers.py +++ b/src/controller/buffers.py @@ -304,11 +304,7 @@ class baseBuffer(object): def get_event(self, ev): """ Parses keyboard input in the ListCtrl and executes the event associated with user keypresses.""" - if ev.GetKeyCode() == wx.WXK_RETURN and ev.ControlDown() and ev.ShiftDown(): event = "pause_audio" - elif ev.GetKeyCode() == wx.WXK_RETURN and ev.ControlDown(): event = "play_audio" - elif ev.GetKeyCode() == wx.WXK_RETURN: event = "open_post" - elif ev.GetKeyCode() == wx.WXK_F5: event = "volume_down" - elif ev.GetKeyCode() == wx.WXK_F6: event = "volume_up" + if ev.GetKeyCode() == wx.WXK_RETURN: event = "open_post" else: event = None ev.Skip() @@ -629,6 +625,17 @@ class audioBuffer(feedBuffer): if self.name == "me_audio": self.tab.post.Enable(True) + def get_event(self, ev): + if ev.GetKeyCode() == wx.WXK_RETURN: event = "play_audio_from_keystroke" + else: + event = None + ev.Skip() + if event != None: + try: + getattr(self, event)() + except AttributeError: + pass + def connect_events(self): widgetUtils.connect_event(self.tab.play, widgetUtils.BUTTON_PRESSED, self.play_audio) widgetUtils.connect_event(self.tab.play_all, widgetUtils.BUTTON_PRESSED, self.play_all) @@ -644,6 +651,13 @@ class audioBuffer(feedBuffer): pub.sendMessage("play", object=self.session.db[self.name]["items"][selected]) return True + def play_audio_from_keystroke(self, *args, **kwargs): + selected = self.tab.list.get_selected() + if selected == -1: + selected = 0 + pub.sendMessage("play", object=self.session.db[self.name]["items"][selected]) + return True + def play_next(self, *args, **kwargs): selected = self.tab.list.get_selected() if selected < 0: From 605f0da751cea03992987740a1218536e0b513b5 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Thu, 25 Apr 2019 09:58:53 -0500 Subject: [PATCH 4/7] Modified worker in player module so it will set stopped to True after finishing playback --- src/presenters/player.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/presenters/player.py b/src/presenters/player.py index e9691b6..3688f04 100644 --- a/src/presenters/player.py +++ b/src/presenters/player.py @@ -19,7 +19,6 @@ from sessionmanager import utils player = None log = logging.getLogger("player") -# This function will be deprecated when the player works with pubsub events, as will no longer be needed to instantiate and import the player directly. def setup(): global player if player == None: @@ -41,12 +40,16 @@ class audioPlayer(object): self.queue = [] # Index of the currently playing track. self.playing_track = 0 + self.playing_all = False + self.worker = RepeatingTimer(5, self.player_function) + self.worker.start() # Status of the player. self.stopped = True # Modify some default settings present in Bass so it will increase timeout connection, thus causing less "connection timed out" errors when playing. bassconfig = BassConfig() # Set timeout connection to 30 seconds. bassconfig["net_timeout"] = 30000 + # subscribe all pubsub events. pub.subscribe(self.play, "play") pub.subscribe(self.play_all, "play-all") pub.subscribe(self.pause, "pause") @@ -82,9 +85,7 @@ class audioPlayer(object): log.exception("error when stopping the file") self.stream = None self.stopped = True - if fresh == True and hasattr(self, "worker") and self.worker != None: - self.worker.cancel() - self.worker = None + if fresh == True: self.queue = [] # Make sure that there are no other sounds trying to be played. if self.is_working == False: @@ -111,9 +112,6 @@ class audioPlayer(object): if self.stream != None and self.stream.is_playing == True: self.stream.stop() self.stopped = True - if hasattr(self, "worker") and self.worker != None: - self.worker.cancel() - self.worker = None self.queue = [] def pause(self): @@ -128,6 +126,8 @@ class audioPlayer(object): self.stopped = False except BassError: pass + if self.playing_all == False and len(self.queue) > 0: + self.playing_all == True @property def volume(self): @@ -157,16 +157,19 @@ class audioPlayer(object): if shuffle: random.shuffle(self.queue) call_threaded(self.play, self.queue[self.playing_track]) - self.worker = RepeatingTimer(5, self.player_function) - self.worker.start() + self.playing_all = True def player_function(self): """ Check if the stream has reached the end of the file so it will play the next song. """ if self.stream != None and self.stream.is_playing == False and self.stopped == False and len(self.stream) == self.stream.position: - if len(self.queue) == 0 or self.playing_track >= len(self.queue): - self.worker.cancel() + if self.playing_track >= len(self.queue): + self.stopped = True + self.playing_all = False return - if self.playing_track < len(self.queue): + elif self.playing_all == False: + self.stopped = True + return + elif self.playing_track < len(self.queue): self.playing_track += 1 self.play(self.queue[self.playing_track]) From a01eabea91f43436ecb7f0c2021514b32669b0b3 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Thu, 25 Apr 2019 10:18:13 -0500 Subject: [PATCH 5/7] Separate playback of voice messages from regular audio playback --- src/controller/buffers.py | 3 +-- src/presenters/player.py | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/controller/buffers.py b/src/controller/buffers.py index d80d7cc..a21dc00 100644 --- a/src/controller/buffers.py +++ b/src/controller/buffers.py @@ -1213,8 +1213,7 @@ class chatBuffer(baseBuffer): a = presenters.displayAudioPresenter(session=self.session, postObject=[attachment["audio"]], interactor=interactors.displayAudioInteractor(), view=views.displayAudio()) elif attachment["type"] == "audio_message": link = attachment["audio_message"]["link_mp3"] - output.speak(_("Playing...")) - pub.sendMessage("play", object=dict(url=link), set_info=False) + pub.sendMessage("play-message", message_url=link) elif attachment["type"] == "link": output.speak(_("Opening URL..."), True) webbrowser.open_new_tab(attachment["link"]["url"]) diff --git a/src/presenters/player.py b/src/presenters/player.py index 3688f04..9dd4ab1 100644 --- a/src/presenters/player.py +++ b/src/presenters/player.py @@ -33,6 +33,7 @@ class audioPlayer(object): self.is_playing = False # This will be the URLStream handler self.stream = None + self.message = None self.vol = config.app["sound"]["volume"] # this variable is set to true when the URLPlayer is decoding something, thus it will block other calls to the play method. self.is_working = False @@ -51,6 +52,7 @@ class audioPlayer(object): bassconfig["net_timeout"] = 30000 # subscribe all pubsub events. pub.subscribe(self.play, "play") + pub.subscribe(self.play_message, "play-message") pub.subscribe(self.play_all, "play-all") pub.subscribe(self.pause, "pause") pub.subscribe(self.stop, "stop") @@ -107,6 +109,19 @@ class audioPlayer(object): self.stopped = False self.is_working = False + def play_message(self, message_url): + if self.message != None and (self.message.is_playing == True or self.message.is_stalled == True): + return self.stop_message() + output.speak(_("Playing...")) + url_ = utils.transform_audio_url(message_url) + url_ = bytes(url_, "utf-8") + try: + self.message = URLStream(url=url_) + except: + log.error("Unable to play URL %s" % (url_)) + return + self.message.play() + def stop(self): """ Stop audio playback. """ if self.stream != None and self.stream.is_playing == True: @@ -114,6 +129,11 @@ class audioPlayer(object): self.stopped = True self.queue = [] + def stop_message(self): + if hasattr(self, "message") and self.message != None and self.message.is_playing == True: + self.message.stop() + self.message = None + def pause(self): """ pause the current playback, without destroying the queue or the current stream. If the stream is already paused this function will resume the playback. """ if self.stream != None: From a917a6a9cd40b41117848280c43193cc6d287839 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Thu, 25 Apr 2019 11:38:00 -0500 Subject: [PATCH 6/7] Ducking when voice messages are being played --- src/presenters/player.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/presenters/player.py b/src/presenters/player.py index 9dd4ab1..8373ac0 100644 --- a/src/presenters/player.py +++ b/src/presenters/player.py @@ -3,6 +3,7 @@ As this player does not have (still) an associated GUI, I have decided to place here the code for the interactor, which connects a bunch of pubsub events, and the presenter itself. """ import sys +import time import random import logging import sound_lib @@ -120,7 +121,13 @@ class audioPlayer(object): except: log.error("Unable to play URL %s" % (url_)) return + self.message.volume = self.vol/100.0 self.message.play() + volume_percent = self.volume*0.25 + volume_step = self.volume*0.15 + while self.stream.volume*100 > volume_percent: + self.stream.volume = self.stream.volume-(volume_step/100) + time.sleep(0.1) def stop(self): """ Stop audio playback. """ @@ -132,6 +139,10 @@ class audioPlayer(object): def stop_message(self): if hasattr(self, "message") and self.message != None and self.message.is_playing == True: self.message.stop() + volume_step = self.volume*0.15 + while self.stream.volume*100 < self.volume: + self.stream.volume = self.stream.volume+(volume_step/100) + time.sleep(0.1) self.message = None def pause(self): @@ -162,7 +173,11 @@ class audioPlayer(object): elif vol > 100: self.vol = 100 if self.stream != None: - self.stream.volume = self.vol/100.0 + if self.message != None and self.message.is_playing: + self.stream.volume = (self.vol*0.25)/100.0 + self.message.volume = self.vol/100.0 + else: + self.stream.volume = self.vol/100.0 def play_all(self, list_of_songs, shuffle=False): """ Play all passed songs and adds all of those to the queue. @@ -181,6 +196,11 @@ class audioPlayer(object): def player_function(self): """ Check if the stream has reached the end of the file so it will play the next song. """ + if self.message != None and self.message.is_playing == False and len(self.message) == self.message.position: + volume_step = self.volume*0.15 + while self.stream.volume*100 < self.volume: + self.stream.volume = self.stream.volume+(volume_step/100) + time.sleep(0.1) if self.stream != None and self.stream.is_playing == False and self.stopped == False and len(self.stream) == self.stream.position: if self.playing_track >= len(self.queue): self.stopped = True From 4ce2e88568ae194220e7eefc68a453a160c1e396 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Thu, 25 Apr 2019 11:38:38 -0500 Subject: [PATCH 7/7] Updated changelog --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index 4f2327e..4b4935f 100644 --- a/changelog.md +++ b/changelog.md @@ -11,6 +11,7 @@ * the audio player module has received some improvements: - When there is something being played, the label of the "play" button, located in all audio buffers, will change automatically to "pause". When pressed, it will pause the song instead of starting the playback again since the beginning. - The last change will work also in the dialog for displaying audio information. Now it's possible to play and pause an audio from such dialog. + - When playing a voice message, if a song is playing already socializer will decrease the volume so you can hear the voice message well enough. Some seconds after the message has finished, the song's volume will be restored. * In audio buffers, you will play the focused audio when pressing enter in the item, instead of opening the audio information dialog. * Removed some old keystrokes in socializer buffers due to better alternatives. The reason for this change is that currently you don't need to be focusing an item in a buffer for being able to use the alternative keystrokes, which makes those a better implementation. - Control+Enter and control+Shift+Enter: superseeded by Control+P for playing (and pausing) the currently focused audio.