From 1fcdd51358fb24f064b2598b83e16d02e91cd1fd Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Fri, 21 Jun 2019 13:07:50 -0500 Subject: [PATCH] Added setting to control the output device in libVLC --- changes.md | 8 +++++--- src/app-configuration.defaults | 1 + src/controller/configuration.py | 23 +++++++++++++++++++++-- src/controller/player.py | 25 ++++++++++++++++++++++++- src/wxUI/configuration.py | 12 +++++++++--- 5 files changed, 60 insertions(+), 9 deletions(-) diff --git a/changes.md b/changes.md index 36da70b..fd720b9 100644 --- a/changes.md +++ b/changes.md @@ -2,14 +2,16 @@ ## Version 0.6 -* Updated VLC libraries and plugins to version 3.0.7. +* Added a settings dialog for the application, from this dialog you will be able to find some general settings, available for MusicDL, and service's settings. Every service defines certain specific settings. +* When transcoding to mp3, the default bitrate now will be 320 KBPS instead of 192. * Added a new and experimental extractor for supporting tidal. - * Take into account that this extractor requires you to have a paid account on tidal. Depending in the account level, you will be able to play and download music in high quality or lossless audio. MusicDL will handle both, though at the current moment, only downloading of lossless audio is implemented. - * There is a new search mode supported in this service. You can retrieve all work for a certain artist by using the protocol artist://, plus the name of the artist you want to retrieve. For example, artist://The beatles will retrieve everything made by the beatles available in the service. The search results will be grouped by albums, compilations and singles, in this order. + * Take into account that this extractor requires you to have a paid account on tidal. Depending in the account level, you will be able to play and download music in high quality or lossless audio. MusicDL will handle both. Lossless audio will be downloaded as flac files, and high quality audio will be downloaded as transcoded 320 KBPS mp3. + * There is a new search mode supported in this service. You can retrieve all work for a certain artist by using the protocol artist://, plus the name of the artist you want to retrieve. For example, artist://The beatles will retrieve everything made by the beatles available in the service. The search results will be grouped by albums, compilations and singles, in this order. Depending in the amount of results to display, this may take a long time. * Due to recent problems with mail.ru and unavailable content in most cases, the service has been removed from MusicDL. * YouTube: * Fixed a long standing issue with playback of some elements, due to Youtube sending encrypted versions of these videos. Now playback should be better. * Updated YoutubeDL to version 2019.6.7 + * Now it is possible to load 50 items for searches as opposed to the previous 20 items limit. This setting can be controlled in the service's preferences * zaycev.net: * Fixed extractor for searching and playing music in zaycev.net. * Unfortunately, it seems this service works only in the russian Federation and some other CIS countries due to copyright reasons. diff --git a/src/app-configuration.defaults b/src/app-configuration.defaults index 8de8402..64ab211 100644 --- a/src/app-configuration.defaults +++ b/src/app-configuration.defaults @@ -1,6 +1,7 @@ [main] volume = integer(default=50) language = string(default="system") +output_device = string(default="") [services] [[tidal]] diff --git a/src/controller/configuration.py b/src/controller/configuration.py index caaf626..fd40626 100644 --- a/src/controller/configuration.py +++ b/src/controller/configuration.py @@ -2,6 +2,7 @@ import config from utils import get_extractors from wxUI.configuration import configurationDialog +from . import player class configuration(object): @@ -12,10 +13,16 @@ class configuration(object): self.save() def create_config(self): - self.view.create_general() + self.output_devices = player.player.get_output_devices() + self.view.create_general(output_devices=[i["name"] for i in self.output_devices]) + current_output_device = config.app["main"]["output_device"] + for i in self.output_devices: + # here we must compare against the str version of the vlc's device identifier. + if str(i["id"]) == current_output_device: + self.view.set_value("general", "output_device", i["name"]) + break extractors = get_extractors() for i in extractors: - print(i) if hasattr(i, "settings"): panel = getattr(i, "settings")(self.view.notebook) self.view.notebook.AddPage(panel, panel.name) @@ -25,6 +32,18 @@ class configuration(object): self.view.realize() def save(self): + selected_output_device = self.view.get_value("general", "output_device") + selected_device_id = None + for i in self.output_devices: + # Vlc returns everything as bytes object whereas WX works with string objects, so I need to convert the wx returned string to bytes before + # Otherwise the comparison will be false. + # toDo: Check if utf-8 would be enough or we'd have to use the fylesystem encode for handling this. + if i["name"] == bytes(selected_output_device, "utf-8"): + selected_device_id = i["id"] + break + if config.app["main"]["output_device"] != selected_device_id: + config.app["main"]["output_device"] = selected_device_id + player.player.set_output_device(config.app["main"]["output_device"]) for i in range(0, self.view.notebook.GetPageCount()): page = self.view.notebook.GetPage(i) if hasattr(page, "save"): diff --git a/src/controller/player.py b/src/controller/player.py index edfc486..102edb3 100644 --- a/src/controller/player.py +++ b/src/controller/player.py @@ -5,6 +5,7 @@ import random import vlc import logging import config +import time from pubsub import pub from utils import call_threaded @@ -33,6 +34,27 @@ class audioPlayer(object): self.event_manager.event_attach(vlc.EventType.MediaPlayerEndReached, self.end_callback) self.event_manager.event_attach(vlc.EventType.MediaPlayerEncounteredError, self.playback_error) log.debug("Bound media playback events.") + # configure output device + self.set_output_device(config.app["main"]["output_device"]) + + def get_output_devices(self): + """ Retrieve enabled output devices so we can switch or use those later. """ + log.debug("Retrieving output devices...") + devices = [] + mods = self.player.audio_output_device_enum() + if mods: + mod = mods + while mod: + mod = mod.contents + devices.append(dict(id=mod.device, name=mod.description)) + mod = mod.next + vlc.libvlc_audio_output_device_list_release(mods) + return devices + + def set_output_device(self, device_id): + """ Set Output device to be ued in LibVLC""" + log.debug("Setting output audio device to {device}...".format(device=device_id,)) + self.player.audio_output_device_set(None, device_id) def play(self, item): self.stopped = True @@ -142,4 +164,5 @@ class audioPlayer(object): def __del__(self): self.event_manager.event_detach(vlc.EventType.MediaPlayerEndReached) - self.event_manager.event_detach(vlc.EventType.MediaPlayerEncounteredError, self.playback_error) \ No newline at end of file + if hasattr(self, "event_manager"): + self.event_manager.event_detach(vlc.EventType.MediaPlayerEncounteredError, self.playback_error) \ No newline at end of file diff --git a/src/wxUI/configuration.py b/src/wxUI/configuration.py index f2caa7d..1001468 100644 --- a/src/wxUI/configuration.py +++ b/src/wxUI/configuration.py @@ -3,9 +3,15 @@ import wx import widgetUtils class general(wx.Panel, widgetUtils.BaseDialog): - def __init__(self, panel): + def __init__(self, panel, output_devices=[]): super(general, self).__init__(panel) sizer = wx.BoxSizer(wx.VERTICAL) + output_device_label = wx.StaticText(self, wx.NewId(), _("Output device")) + self.output_device = wx.ComboBox(self, wx.NewId(), choices=output_devices, value=output_devices[0], style=wx.CB_READONLY) + output_device_box = wx.BoxSizer(wx.HORIZONTAL) + output_device_box.Add(output_device_label, 0, wx.ALL, 5) + output_device_box.Add(self.output_device, 0, wx.ALL, 5) + sizer.Add(output_device_box, 0, wx.ALL, 5) self.SetSizer(sizer) class configurationDialog(widgetUtils.BaseDialog): @@ -16,8 +22,8 @@ class configurationDialog(widgetUtils.BaseDialog): self.sizer = wx.BoxSizer(wx.VERTICAL) self.notebook = wx.Treebook(self.panel) - def create_general(self): - self.general = general(self.notebook) + def create_general(self, output_devices=[]): + self.general = general(self.notebook, output_devices=output_devices) self.notebook.AddPage(self.general, _("General")) self.general.SetFocus()