From 5a3535132e904822ecaf7184b2f46b6d1f630e6f Mon Sep 17 00:00:00 2001 From: Spiros Georgaras Date: Tue, 24 Nov 2020 14:02:18 +0200 Subject: [PATCH] - Version 0.8.8-beta6 - Fixing playback for m3u8 on vlc - Fixing "-u" functionality on Windows - Fixing "no player" messages - Stopping runaway threads from displaying messages after the player is stopped --- Changelog | 16 +++++++- pyradio/__init__.py | 2 +- pyradio/browser.py | 2 - pyradio/common.py | 6 +++ pyradio/log.py | 22 +++++++++++ pyradio/main.py | 4 +- pyradio/player.py | 77 +++++++++++++++++++++++--------------- pyradio/radio.py | 91 ++++++++++++++++----------------------------- windows.html | 4 +- windows.md | 4 +- 10 files changed, 129 insertions(+), 99 deletions(-) diff --git a/Changelog b/Changelog index 9a5d6c27..61645fb4 100644 --- a/Changelog +++ b/Changelog @@ -1,5 +1,17 @@ -2020-11-17 - * Versio 0.8.8-beta4 +2020-11-23 s-n-g + * Version 0.8.8-beta6 + * Fixing playback for m3u8 (vlc) + * Fixing "-u" functionality on Windows + * Fixing "no player" messages + * Stopping runaway threads from displaying messages + after the player is stopped + +2020-11-23 s-n-g + * Version 0.8.8-beta5 + * Adding VLC support on Windows + +2020-11-17 s-n-g + * Version 0.8.8-beta4 * Adding the "Force http connections" configuration option (#113) * Fixed a couple of bugs diff --git a/pyradio/__init__.py b/pyradio/__init__.py index dec014e2..c204aee8 100644 --- a/pyradio/__init__.py +++ b/pyradio/__init__.py @@ -6,7 +6,7 @@ # New stable version: '' # Beta version: 'betax', x=1,2,3... # RC version: 'RCx', x=1,23... -app_state = 'beta5' +app_state = 'beta6' __version__ = version = '.'.join(map(str, version_info)) __project__ = __name__ diff --git a/pyradio/browser.py b/pyradio/browser.py index a0d5fb45..57bc5727 100644 --- a/pyradio/browser.py +++ b/pyradio/browser.py @@ -805,9 +805,7 @@ def get_data_dict(data): logger.info('Asked to stop after working on "{}"...'.format(an_item)) self._terminated = True return - # print('I am here') lock.acquire() - # print('I am here too') callback(my_data, ret) lock.release() diff --git a/pyradio/common.py b/pyradio/common.py index a63cd90e..bc4bfeda 100644 --- a/pyradio/common.py +++ b/pyradio/common.py @@ -22,3 +22,9 @@ def BACKGROUND(): return 1 ('Normal Cursor', 6, 0, 0), ('Active Cursor', 9, 0, 0), ('Edit Cursor', 8, 0, 0 ) ) + +""" Messages to display when player starts / stops + Used in log to stop runaway threads from printing + messages after playback is stopped """ +player_start_stop_token = ('Initialization: "', ': Playback stopped') + diff --git a/pyradio/log.py b/pyradio/log.py index 9f1e6417..296eb9cd 100644 --- a/pyradio/log.py +++ b/pyradio/log.py @@ -3,6 +3,7 @@ from sys import version_info import logging import threading +from .common import player_start_stop_token logger = logging.getLogger(__name__) @@ -22,6 +23,8 @@ class Log(object): lock = threading.Lock() + _player_stopped = 0 + def __init__(self): self.width = None @@ -45,9 +48,24 @@ def _do_i_print_last_char(self, first_print): def write(self, msg=None, suffix=None, counter=None, help_msg=False, error_msg=False, notify_function=None): if self.cursesScreen: with self.lock: + if msg: + if player_start_stop_token[1] in msg: + self._player_stopped += 1 + elif msg.startswith(player_start_stop_token[0]): + self._player_stopped = 0 + if msg and self._player_stopped > 1: + """ Refuse to print anything if "Playback stopped" + was the last message printed + """ + if logger.isEnabledFor(logging.DEBUG): + logger.debug('Refusing to show message; player is stopped: "{}"'.format(msg)) + #return + elif self._player_stopped == 1: + self._player_stopped = 2 if self.asked_to_stop: self.asked_to_stop = False self.counter = None + self._player_stopped = 0 return #if logger.isEnabledFor(logging.DEBUG): # logger.debug('before ----------------------------') @@ -76,6 +94,7 @@ def write(self, msg=None, suffix=None, counter=None, help_msg=False, error_msg=F if self.asked_to_stop: self.asked_to_stop = False self.counter = None + self._player_stopped = 0 return """ update main message """ if self.msg: @@ -101,6 +120,7 @@ def write(self, msg=None, suffix=None, counter=None, help_msg=False, error_msg=F if self.asked_to_stop: self.asked_to_stop = False self.counter = None + self._player_stopped = 0 return """ display suffix """ if self.suffix: @@ -153,8 +173,10 @@ def write(self, msg=None, suffix=None, counter=None, help_msg=False, error_msg=F if self.asked_to_stop: self.asked_to_stop = False self.counter = None + self._player_stopped = 0 return self.cursesScreen.refresh() + #logger.error('DE _player_stopped = {}'.format(self._player_stopped)) def readline(self): pass diff --git a/pyradio/main.py b/pyradio/main.py index 382ccfec..9ff20900 100644 --- a/pyradio/main.py +++ b/pyradio/main.py @@ -141,8 +141,7 @@ def shell(): sys.exit(1) if args.use_player != '': - if not platform.startswith('win'): - requested_player = args.use_player + requested_player = args.use_player if args.list is False and args.add is False: print('Reading playlist...') @@ -229,7 +228,6 @@ def print_playlist_selection_error(a_selection, cnf, ret, exit_if_malformed=True if exit_if_malformed: if ret == -1: print('Error: playlist is malformed: "{}"'.format(a_selection)) - #print('Error: playlist is malformed: "{}"'.format(args.stations)) sys.exit(1) if ret == -2: diff --git a/pyradio/player.py b/pyradio/player.py index 87ff9e6d..89c69b98 100644 --- a/pyradio/player.py +++ b/pyradio/player.py @@ -444,6 +444,14 @@ def _do_save_volume(self, config_string): self.PROFILE_FROM_USER = True return ret_string + def _stop_delay_thread(self): + if self.delay_thread is not None: + try: + self.delay_thread.cancel() + except: + pass + self.delay_thread = None + def _is_in_playback_token(self, a_string): for a_token in self._playback_token_tuple: if a_token in a_string: @@ -511,6 +519,7 @@ def updateStatus(self, *args): new_input = self.oldUserInput['Title'] self.outputStream.write(msg=new_input, counter='') self.playback_is_on = True + self._stop_delay_thread() if 'AO: [' in subsystemOut: with self.status_update_lock: if version_info > (3, 0): @@ -535,7 +544,13 @@ def updateStatus(self, *args): #logger.error("***** icy_entry") title = self._format_title_string(subsystemOut) ok_to_display = False + if not self.playback_is_on: + if logger.isEnabledFor(logging.INFO): + logger.info('*** updateStatus(): Start of playback detected (Icy-Title received) ***') + self.playback_is_on = True if title[len(self.icy_title_prefix):].strip(): + #self._stop_delay_thread() + #logger.error("***** updating title") self.oldUserInput['Title'] = title # make sure title will not pop-up while Volume value is on if self.delay_thread is None: @@ -543,6 +558,9 @@ def updateStatus(self, *args): if ok_to_display and self.playback_is_on: string_to_show = self.title_prefix + title self.outputStream.write(msg=string_to_show, counter='') + else: + if logger.isEnabledFor(logging.debug): + logger.debug('***** Title change inhibited: ok_to_display = {0}, playbabk_is_on = {1}'.format(ok_to_display, self.playback_is_on)) else: ok_to_display = True if (logger.isEnabledFor(logging.INFO)): @@ -560,10 +578,14 @@ def updateStatus(self, *args): else: for a_token in self.icy_audio_tokens.keys(): if a_token in subsystemOut: - logger.error(' DE token = "{}"'.format(a_token)) - logger.error(' DE icy_audio_tokens[a_token] = "{}"'.format(self.icy_audio_tokens[a_token])) + if not self.playback_is_on: + if logger.isEnabledFor(logging.INFO): + logger.info('*** updateStatus(): Start of playback detected (Icy audio token received) ***') + self.playback_is_on = True + #logger.error(' DE token = "{}"'.format(a_token)) + #logger.error(' DE icy_audio_tokens[a_token] = "{}"'.format(self.icy_audio_tokens[a_token])) a_str = subsystemOut.split(a_token) - logger.error(' DE str = "{}"'.format(a_str)) + #logger.error(' DE str = "{}"'.format(a_str)) with self.status_update_lock: if self.icy_audio_tokens[a_token] == 'icy-br': self._icy_data[self.icy_audio_tokens[a_token]] = a_str[1].replace('kbit/s', '') @@ -742,7 +764,7 @@ def updateWinVLCStatus(self, *args): except: pass if (not self.playback_is_on) and (logger.isEnabledFor(logging.INFO)): - logger.info('*** updateStatus(): Start of playback detected ***') + logger.info('*** updateWinVLCStatus(): Start of playback detected ***') #if self.outputStream.last_written_string.startswith('Connecting to'): if self.oldUserInput['Title'] == '': new_input = 'Playing: "{}"'.format(self.name) @@ -761,6 +783,10 @@ def updateWinVLCStatus(self, *args): elif self._is_icy_entry(subsystemOut): if stop(): break + if not self.playback_is_on: + if logger.isEnabledFor(logging.INFO): + logger.info('*** updateWinVLCStatus(): Start of playback detected (Icy-Title received) ***') + self.playback_is_on = True #logger.error("***** icy_entry") title = self._format_title_string(subsystemOut) @@ -792,10 +818,14 @@ def updateWinVLCStatus(self, *args): break for a_token in self.icy_audio_tokens.keys(): if a_token in subsystemOut: - logger.error(' DE token = "{}"'.format(a_token)) - logger.error(' DE icy_audio_tokens[a_token] = "{}"'.format(self.icy_audio_tokens[a_token])) + if not self.playback_is_on: + if logger.isEnabledFor(logging.INFO): + logger.info('*** updateWinVLCStatus(): Start of playback detected (Icy audio token received) ***') + self.playback_is_on = True + #logger.error(' DE token = "{}"'.format(a_token)) + #logger.error(' DE icy_audio_tokens[a_token] = "{}"'.format(self.icy_audio_tokens[a_token])) a_str = subsystemOut.split(a_token) - logger.error(' DE str = "{}"'.format(a_str)) + #logger.error(' DE str = "{}"'.format(a_str)) with self.status_update_lock: if self.icy_audio_tokens[a_token] == 'icy-br': self._icy_data[self.icy_audio_tokens[a_token]] = a_str[1].replace('kbit/s', '') @@ -988,15 +1018,12 @@ def _set_mpv_playback_is_on(self, stop): def threadUpdateTitle(self, delay=1): if self.oldUserInput['Title'] != '': - try: - self.delay_thread.cancel() - except: - pass + self._stop_delay_thread() try: self.delay_thread = threading.Timer(delay, self.updateTitle, [ self.outputStream, - self.title_prefix + self._format_title_string(self.oldUserInput['Title']) ] + None ] ) self.delay_thread.start() except: @@ -1004,7 +1031,11 @@ def threadUpdateTitle(self, delay=1): logger.debug("delay thread start failed") def updateTitle(self, *arg, **karg): - arg[0].write(msg=arg[1]) + self._stop_delay_thread() + if arg[1]: + arg[0].write(msg=arg[1]) + else: + arg[0].write(msg=self.title_prefix + self._format_title_string(self.oldUserInput['Title'])) def _is_icy_entry(self, a_string): #logger.error("**** a_string = {}".format(a_string)) @@ -1044,6 +1075,7 @@ def play(self, name, streamUrl, encoding = ''): self.show_volume = True self.title_prefix = '' self.playback_is_on = False + self.delay_thread = None self.outputStream.write(msg='Station: "{}" - Opening connection...'.format(name), counter='') if logger.isEnabledFor(logging.INFO): logger.info('Selected Station: "{}"'.format(name)) @@ -1130,11 +1162,7 @@ def close(self): self.connection_timeout_thread.join() except: pass - if self.delay_thread is not None: - try: - self.delay_thread.cancel() - except: - pass + self._stop_delay_thread() if self.process is not None: if platform.startswith('win'): try: @@ -1165,11 +1193,7 @@ def toggleMute(self): self.muted = not self.muted self._mute() if self.muted: - if self.delay_thread is not None: - try: - self.delay_thread.cancel() - except: - pass + self._stop_delay_thread() self.title_prefix = '[Muted] ' self.show_volume = False else: @@ -1691,7 +1715,6 @@ class VlcPlayer(Player): WIN = False if platform.startswith('win'): WIN = True - logger.error('DE WIN = {}'.format(WIN)) PLAYER_CMD = "cvlc" if WIN: # TODO: search and finde vlc.exe @@ -1708,8 +1731,6 @@ class VlcPlayer(Player): else: executable_found = False - print('executable: {}'.format(REAL_PLAYER_CMD)) - if executable_found: """ items of this tupple are considered icy-title and get displayed after first icy-title is received """ @@ -1734,7 +1755,7 @@ class VlcPlayer(Player): max_volume = 512 """ When found in station transmission, playback is on """ - _playback_token_tuple = ('main audio ', 'Content-Type: audio' ) + _playback_token_tuple = ('main audio ', 'Content-Type: audio', ' Segment #' ) """ Windows only variables """ _vlc_stdout_log_file = '' @@ -1951,7 +1972,6 @@ def _req(self, msg, ret_function=None, full=True): try: while (True): received = (sock.recv(4096)).decode() - #print('received: "{}"'.format(received)) response = response + received if full: if response.count("\r\n") > 1: @@ -1963,7 +1983,6 @@ def _req(self, msg, ret_function=None, full=True): break except: response = response + received - pass sock.close() except: pass diff --git a/pyradio/radio.py b/pyradio/radio.py index 8d3f6541..fa118170 100644 --- a/pyradio/radio.py +++ b/pyradio/radio.py @@ -487,61 +487,37 @@ def initBody(self): self.bodyWin.noutrefresh() self.outerBodyWin.noutrefresh() if self.ws.operation_mode == self.ws.NO_PLAYER_ERROR_MODE: - if platform.startswith('win'): - txt = '''PyRadio cannot find mplayer. - - This means that either mplayer is not installed in this system, - or its directory is not in the PATH. - - Please refer to windows.html help file to fix this problem. + if self.requested_player: + if self.requested_player in ('mpv', 'mplayer', 'vlc', 'cvlc'): + atxt = """PyRadio is not able to use the player you specified. - To get to this file, execute "pyradio -ocd" and navigate to - the "help" directory.''' - else: - if self.requested_player: - if self.requested_player in ('mpv', 'mplayer', 'vlc', 'cvlc'): - atxt = """PyRadio is not able to use the player you specified. - - This player ({}) is supported by PyRadio, but it probably - is not installed in your system. + This player ({}) is supported by PyRadio, but it probably + is not installed in your system. - Keep in mind that you can choose a player to use by specifying - the "-u" command line parameter.""" - txt = atxt.format(self.requested_player) + Keep in mind that you can choose a player to use by specifying + the "-u" command line parameter.""" + txt = atxt.format(self.requested_player) - else: - txt = """PyRadio is not able to use the player you specified. - - This means that either this particular player is not supported - by PyRadio, or that you have simply misspelled its name. - - PyRadio currently supports three players: mpv, mplayer and vlc, - automatically detected in this order.""" else: - txt = """PyRadio is not able to detect any players. + txt = """PyRadio is not able to use the player you specified. + + This means that either this particular player is not supported + by PyRadio, or that you have simply misspelled its name. PyRadio currently supports three players: mpv, mplayer and vlc, - automatically detected in this order. + automatically detected in this order.""" + else: + txt = """PyRadio is not able to detect any players. + + PyRadio currently supports three players: mpv, mplayer and vlc, + automatically detected in this order. - Please install any one of them and try again.""" + Please install any one of them and try again.""" + if platform.startswith('win'): + txt = txt.replace('mpv, ', '') + txt = txt.replace('three', 'two') self.refreshNoPlayerBody(txt) else: - #if self.ws.operation_mode == self.ws.MAIN_HELP_MODE: - # self.ws.operation_mode = self.ws.window_mode = self.ws.NORMAL_MODE - # self.helpWin = None - # if logger.isEnabledFor(logging.DEBUG): - # logger.debug('MODE: self.ws.MAIN_HELP_MODE => self.ws.NORMAL_MODE') - #elif self.ws.operation_mode == self.ws.PLAYLIST_HELP_MODE: - # self.ws.operation_mode = self.ws.window_mode = self.ws.PLAYLIST_MODE - # self.helpWin = None - # if logger.isEnabledFor(logging.DEBUG): - # logger.debug('MODE: self.ws.PLAYLIST_HELP_MODE => self.ws.PLAYLIST_MODE') - #elif self.ws.operation_mode == self.ws.THEME_HELP_MODE: - # self.ws.operation_mode = self.ws.window_mode = self.ws.THEME_MODE - # self.helpWin = None - # if logger.isEnabledFor(logging.DEBUG): - # logger.debug('MODE: self.ws.THEME_HELP_MODE => self.ws.THEME_MODE') - # make sure selected is visible self._put_selection_in_the_middle() self.refreshBody() @@ -692,16 +668,13 @@ def __displayBodyLine(self, lineNum, pad, station): def run(self): if self.ws.operation_mode == self.ws.NO_PLAYER_ERROR_MODE: - if platform.startswith('win'): - self.log.write(msg='mplayer not found. Press any key to exit....', error_msg=True) - else: - if self.requested_player: - if ',' in self.requested_player: - self.log.write(msg='None of "{}" players is available. Press any key to exit....'.format(self.requested_player), error_msg=True) - else: - self.log.write(msg='Player "{}" not available. Press any key to exit....'.format(self.requested_player), error_msg=True) + if self.requested_player: + if ',' in self.requested_player: + self.log.write(msg='None of "{}" players is available. Press any key to exit....'.format(self.requested_player), error_msg=True) else: - self.log.write(msg="No player available. Press any key to exit....", error_msg=True) + self.log.write(msg='Player "{}" not available. Press any key to exit....'.format(self.requested_player), error_msg=True) + else: + self.log.write(msg="No player available. Press any key to exit....", error_msg=True) try: self.bodyWin.getch() except KeyboardInterrupt: @@ -855,6 +828,8 @@ def setStation(self, number): self.startPos = self.selection def playSelectionBrowser(self): + self.log.display_help_message = False + self.log.write(msg=player_start_stop_token[0] + self._last_played_station[0] + '"') #### self._cnf.browsing_station_service = True # Add a history item to preserve browsing_station_service # Need to add TITLE, if service found @@ -872,6 +847,8 @@ def playSelection(self): self.playing = self.selection self._last_played_station = self.stations[self.selection] stream_url = '' + self.log.display_help_message = False + self.log.write(msg=player_start_stop_token[0] + self._last_played_station[0] + '"') if self._cnf.browsing_station_service: if self._cnf.online_browser.have_to_retrieve_url: self.log.display_help_message = False @@ -883,8 +860,6 @@ def playSelection(self): enc = self.stations[self.selection][2].strip() except: enc = '' - self.log.display_help_message = False - self.log.write(msg='Initialization: "' + self._last_played_station[0] + '"') try: self.player.play(self._last_played_station[0], stream_url, self.get_active_encoding(enc)) except OSError: @@ -960,7 +935,7 @@ def stopPlayer(self, show_message=True): self._show_player_is_stopped() def _show_player_is_stopped(self): - self.log.write(msg='{}: Playback stopped'.format(self._format_player_string()), help_msg=True, suffix=self._status_suffix, counter='') + self.log.write(msg='{}'.format(self._format_player_string()) + player_start_stop_token[1], help_msg=True, suffix=self._status_suffix, counter='') def removeStation(self): if self._cnf.confirm_station_deletion and not self._cnf.is_register: diff --git a/windows.html b/windows.html index 890411ad..fbfefedc 100644 --- a/windows.html +++ b/windows.html @@ -74,7 +74,7 @@

How it all works First of all, let me tell you that if you are still running Windows XP, you can just stop reading right now; it won’t happen…

Then, due to reasons that are of no importance right now, mpv is not (yet?) supported. That leaves us with MPlayer and VLC.

Installing MPlayer takes a couple of extra steps, and you may find that some streams (e.g. m3u8) may not be playable. Furthermore, special care has to be taken in order to be able to save the volume of the player.

-

VLC is much easier to install, but song titles’ updating may not be 100% consistaent. If this is not a deal breaker for you, then just go on and use VLC as PyRadio’s player.

+

VLC is much easier to install, but song titles’ updating may not be 100% consistent (if any). If this is not a deal breaker for you, then just go on and use VLC as PyRadio’s player.

Other than that, you will have a fully functional PyRadio installation.

Having said that, let us proceed with the installation.

Installation Top

@@ -125,7 +125,7 @@

2. Player installation Cons Extra steps to install
May not play all streams (e.g. m3u8) -Titles update is not consistent +Titles update is not consistent (if any)) diff --git a/windows.md b/windows.md index 05636494..680bfc4b 100644 --- a/windows.md +++ b/windows.md @@ -46,7 +46,7 @@ Then, due to reasons that are of no importance right now, [mpv](https://mpv.io/) Installing [MPlayer](http://www.mplayerhq.hu/) takes a couple of extra steps, and you may find that some streams (e.g. m3u8) may not be playable. Furthermore, special care has to be taken in order to be able to save the volume of the player. -[VLC](https://www.videolan.org/vlc/) is much easier to install, but song titles' updating may not be 100% consistaent. If this is not a deal breaker for you, then just go on and use [VLC](https://www.videolan.org/vlc/) as **PyRadio**'s player. +[VLC](https://www.videolan.org/vlc/) is much easier to install, but song titles' updating may not be 100% consistent (if any). If this is not a deal breaker for you, then just go on and use [VLC](https://www.videolan.org/vlc/) as **PyRadio**'s player. Other than that, you will have a fully functional **PyRadio** installation. @@ -101,7 +101,7 @@ This is what you should know before making your decision: | | MPlayer | VLC | |----------|----------------------------------------------------------------|-----------------------------------------------| | **Pros** | Fully functional | Easy installation
Plays almost all streams | -| **Cons** | Extra steps to install
May not play all streams (e.g. m3u8) | Titles update is not consistent | +| **Cons** | Extra steps to install
May not play all streams (e.g. m3u8) | Titles update is not consistent (if any)) | #### 2.1 MPlayer installation