New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Move player logic out of main #513
base: master
Are you sure you want to change the base?
Move player logic out of main #513
Conversation
pithos/GstPlayer.py
Outdated
_desired_state = PseudoGst.STOPPED | ||
_actual_state = PseudoGst.STOPPED | ||
_buffering_timer_id = 0 | ||
_update_proxy = True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[flake8]
- [W291] trailing whitespace
pithos/GstPlayer.py
Outdated
self._settings = settings | ||
self._settings.connect('changed::proxy', lambda *ignore: setattr(self, '_update_proxy', True)) | ||
|
||
elements = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[flake8]
- [W291] trailing whitespace
pithos/GstPlayer.py
Outdated
return GstPbutils.install_plugins_installation_in_progress() | ||
|
||
def query_position(self): | ||
if self.query(self.__query_position): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[flake8]
- [E111] indentation is not a multiple of four
pithos/GstPlayer.py
Outdated
|
||
def query_position(self): | ||
if self.query(self.__query_position): | ||
return self.__query_position.parse_position()[1] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[flake8]
- [E111] indentation is not a multiple of four
pithos/GstPlayer.py
Outdated
def query_position(self): | ||
if self.query(self.__query_position): | ||
return self.__query_position.parse_position()[1] | ||
else: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[flake8]
- [E111] indentation is not a multiple of four
pithos/GstPlayer.py
Outdated
if self.query(self.__query_position): | ||
return self.__query_position.parse_position()[1] | ||
else: | ||
return 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[flake8]
- [E111] indentation is not a multiple of four
pithos/GstPlayer.py
Outdated
self._set_player_state(PseudoGst.STOPPED, change_gst_state=True) | ||
|
||
def _query_duration(self): | ||
if self.query(self.__query_duration): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[flake8]
- [E111] indentation is not a multiple of four
pithos/GstPlayer.py
Outdated
|
||
def _query_duration(self): | ||
if self.query(self.__query_duration): | ||
return self.__query_duration.parse_duration()[1] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[flake8]
- [E111] indentation is not a multiple of four
pithos/GstPlayer.py
Outdated
if GstPbutils.is_missing_plugin_message(message): | ||
if GstPbutils.install_plugins_supported(): | ||
details = GstPbutils.missing_plugin_message_get_installer_detail(message) | ||
GstPbutils.install_plugins_async([details,], None, self.on_gst_plugin_installed, None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[flake8]
- [E231] missing whitespace after ','
pithos/GstPlayer.py
Outdated
_('Missing codec'), | ||
None, | ||
_('GStreamer is missing a plugin and it could not be automatically installed.' | ||
'\nEither manually install it or try another quality setting.'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[flake8]
- [E127] continuation line over-indented for visual indent
pithos/pithos.py
Outdated
gi.require_version('GstPbutils', '1.0') | ||
from gi.repository import Gst, GstAudio, GstPbutils, GObject, Gtk, Gdk, Pango, GdkPixbuf, Gio, GLib | ||
from gi.repository import Gst, GstPbutils, GObject, Gtk, Gdk, Pango, GdkPixbuf, Gio, GLib |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[flake8]
- [F401] 'gi.repository.GstPbutils' imported but unused
pithos/pithos.py
Outdated
# Edge case. We might get this singal while we're reconnecting to Pandora. | ||
# If so self.current_song will be None. | ||
if self.current_song is None: | ||
return | ||
# Fallback to using song.trackLength which is in seconds and converted to nanoseconds | ||
self.current_song.duration = self.query_duration() or self.current_song.trackLength * Gst.SECOND | ||
self.current_song.duration = duration or self.current_song.trackLength * Gst.SECOND |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[flake8]
- [F405] 'Gst' may be undefined, or defined from star imports: .pandora, .pandora.data
pithos/GstPlayer.py
Outdated
self.props.proxy_pw = password | ||
logging.debug( | ||
'\nSetting GstPlayer Proxy:' | ||
'\nHTTP proxy server URI: {}' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Flake8]
continuation line unaligned for hanging indent
pithos/GstPlayer.py
Outdated
elements[1].props.expose_all_streams = False | ||
elements[2].props.use_buffering = True | ||
elements[2].props.max_size_bytes = 0 | ||
elements[2].props.max_size_time = MAX_SIZE_TIME |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Flake8]
trailing whitespace
pithos/GstPlayer.py
Outdated
lambda *ignore: self.emit('eos'), | ||
) | ||
self.bus.connect( | ||
'message::error', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Flake8]
trailing whitespace
@TingPing, I started over and switched back to just using playbin. I was getting random memory leaks with the custom pipeline? |
Maybe make a minimal reproducer and figure it out with upstream. Though if its a PyGObject problem that might be messier to figure out. I'm sure you wouldn't be interested in it but if the results are better using C is always acceptable IMO. |
So how would we do that? Write the player in C and write our own python bindings for it? |
My theory is that the buffer isn't always getting properly cleared. If you let songs finish on their own for the most part there's no real leak. But if you skip songs manually it will just eat memory most of the time. |
Playbin seems to workaround this because it generates a new pipeline for every song and I assume unrefs the entire old pipeline? |
pithos/GstPlayer.py
Outdated
for prop, value, in props.items(): | ||
if value is not None and hasattr(soup, prop): | ||
setattr(soup, prop, value) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Flake8]
blank line at end of file
pithos/GstPlayer.py
Outdated
self, | ||
'eq-band{}'.format(i), | ||
GObject.BindingFlags.BIDIRECTIONAL | GObject.BindingFlags.SYNC_CREATE, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Flake8]
continuation line missing indentation or outdented
pithos/GstPlayer.py
Outdated
self._set_player_state(PseudoGst.PAUSED) | ||
|
||
def stop(self): | ||
self._prerolled = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Flake8]
trailing whitespace
pithos/GstPlayer.py
Outdated
# check to see if we're buffering. (we more than likely am) | ||
if not self._prerolled: | ||
self._prerolled = True | ||
self._on_gst_buffering() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Flake8]
trailing whitespace
@TingPing I think I figured out the memory leak. In my previous pipeline I had the queue2 element after the decodbin element. Moving it in front of decodebin seems to fixed it. Also I think I found out why download buffering was locking up. In a pipeline it's possible to get buffering messages before the async-done message. The 1st async-done message going from NULL to PAUSED means that all elements in the pipeline have completed the state change and the pipeline is prerolled. In our case I think we were trying to go to PLAYING before the pipeline was prerolled every once in a great while. In this new pipeline I ignore all buffering messages until the pipeline is prerolled. I'll do some testing but we may be able to add an option to enable download buffering as an option. an added benefit to the custom pipeline is that we can set the audio to download to the same folder as the cover art. |
pithos/pithos.py
Outdated
try: | ||
self.tempdir = tempfile.TemporaryDirectory(prefix='pithos-', | ||
dir=GLib.get_user_cache_dir()) | ||
logging.info("Created temporary directory %s" %self.tempdir.name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Flake8]
missing whitespace around operator
pithos/GstPlayer.py
Outdated
blurb='actual state of GstPlayer PseudoGst Enum', | ||
flags=GObject.ParamFlags.READABLE, | ||
) | ||
def actual_state(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Flake8]
trailing whitespace
pithos/GstPlayer.py
Outdated
return GstPbutils.install_plugins_installation_in_progress() | ||
|
||
def query_position(self): | ||
if self.query(self.__query_position): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Flake8]
trailing whitespace
pithos/GstPlayer.py
Outdated
|
||
def _query_duration(self): | ||
if self.query(self.__query_duration): | ||
return self.__query_duration.parse_duration()[1] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Flake8]
trailing whitespace
@TingPing I think I'm pretty much done with this if you could take a look at it please? It does use the downloadbuffer element but in using it myself over the last couple weeks it seems that whatever the problem was either has been resolved and/or my usage of the element is more "correct"? In any case we could provide a pref to disable it for users of older Gstreamer versions if it causes them issues. It wouldn't be hard. My pipeline already falls back to stream buffering if the cache folder couldn't be created. (audio is cached to the same folder as the art). |
All things being equal I haven't noticed a difference? I'll do a quick test to make sure though... |
Nope, I notice no difference. It's really hard to say one way or another without hearing what you're hearing but there's nothing really different going on with the volume. AFAIK the volume element is used for volume in playbin? It's also hard to measure objectively by ear the subjective apparent volume based on different songs? |
Toggling the normalization plugin isn't the cause btw. But it isn't a subtle difference, 100% on this branch is about as loud as 50% on master. |
Does the volume in the system audio settings slider match the volume controls in Pithos? |
Another option is to use pulsesink directly instead of autoaudiosink. Pulsesink has built in volume and since it's designed for pulseaudio it should work perfectly. It would also allow us to set the pulseprops. |
As a side note to my previous comment, we already assume pulseaudio in our volume value conversions. |
Nope, |
PulseAudio pretty much gobbles up all audio. The only other audio backend really is ALSA and it's not really a backend per say but the layer under Pulseaudio right above the hardware. PulseAudio even goes so far to pretend to be ALSA so that applications that aren't PulseAudio aware(not many these days) work with PulseAudio. The effect is that you have to be trying pretty hard to talk directly to ALSA otherwise you'll get Pulseaudio. The autoaudiosink element will automatically create and use pulsesink if it detects PulseAudio otherwise it will use alsasink. pulsesink as I said has volume built in, alsasink does not. That's why I have a volume element in the pipeline. |
Right, I meant maybe its incorrectly loading alsasink here or something. I'm sure somebody would be mad if we depended upon Pulseaudio but I don't know if we should care. |
It's not hard to detect pulse and act accordingly. |
adapted from quodlibet: def _pulse_is_running(self):
# If we have a pulsesink we can get the server presence through
# setting the ready state
element = Gst.ElementFactory.make('pulsesink', None)
if element is not None:
element.set_state(Gst.State.READY)
res = element.get_state(0)[0]
element.set_state(Gst.State.NULL)
return res != Gst.StateChangeReturn.FAILURE
return False |
If pulse is running we use pulsesink with it's pulse friendly built in volume. If not we use a separate volume and autoaudiosink binding the player's volume prop accordingly. The rest of Pithos would be none the wiser. |
There @TingPing test the branch out again and see if this fixes your volume issue. The latest changes are: No download buffering. Auto detect pulseaudio. |
Worked |
Ok, I got a little bit a tweaking to do and it'll be ready I think. |
I would like to get a 1.4.1 bugfix release out soon (will wait for a fix the mediakeys issue I commented on) and then we can merge this and work towards 1.5.0. |
OK. I'm fine with that. I'd like 1.5 to be a pretty big release. This, UI redesign and main logic clean up. |
@TingPing, on the subject of download buffering; what about just doing a version check? Something like: GST_MAJOR_VERSION, GST_MINOR_VERSION = Gst.version()[0:2]
_USE_DOWNLOADBUFFER = GST_MAJOR_VERSION > 1 or GST_MINOR_VERSION > 11 I really don't want to test every version of Gstreamer 1.x to see where it's fixed, but I know that download buffering in 1.12 works. This seems to solve your objection of exposing internal details? |
I believe due to our build dependencies we are only installable on distros with GStreamer 1.8 and newer anyway. If you really don't want to test that anyway I suppose I can live with this. |
I know it's broken in 1.8.x, at least on Ubuntu 16.04. That really only leaves 1.10.x. Which comes with the current Debian stable and Ubuntu 17.04 neither of which I really care about or care to test.(one being crusty and the other only supported for 9 months and not current) It will work with flatpak and non-stagnant distros. I'd like to make a reasonable effort to not break Pithos on crusty distros but my thought is that I really don't want to be held back by them either on the master branch. If a user wants the latest and greatest version of Pithos they can install the flatpak otherwise they can install the version that comes with their distro of choice. If they aren't happy with that version they need to talk to the Pithos package maintainer for that distro about backporting bug fixes and features and dealing with dependencies. |
pithos/GstPlayer.py
Outdated
duration = self._query_duration() | ||
if duration or final: | ||
self._got_duration = True | ||
self.emit('new-stream-duration', duration) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Flake8]
trailing whitespace
pithos/pithos.py
Outdated
try: | ||
self.tempdir = tempfile.TemporaryDirectory(prefix='pithos-', | ||
dir=GLib.get_user_cache_dir()) | ||
logging.info("Created temporary directory %s" %self.tempdir.name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Flake8]
missing whitespace around operator
Well @TingPing, I've been abusing the pipeline for the last couple days by changing songs machine gun style and even in Gstreamer 1.12 I've been able to lockup the downloadbuffer a couple times so download buffering is still out :'( On the up side I've been able to improve other aspects of the pipeline. Here's a rundown on what I've got so far:
|
On a side note it might be nice to expose the buffer size in secs as a user pref. I know you're not a fan of exposing internal details but I don't think it's unreasonable to expose a buffer size setting for a network streaming app. It would allow the user to find the sweet spot for their connection. A reasonable default could be between 3 and 5 secs and a reasonable max could be 10 secs and reasonable min would be 2. |
Flake8 detected 11 issues on 772c6e3 |
@JasonLG1979 Maybe worth looking into: http://base-art.net/Articles/gstreamers-playbin3-overview-for-application-developers/ |
Still a wip at the moment but I wanted to put in the PR so we could polish it up. currently this PR creates a custom player pipeline and abstracts as much out as possible as far as I can tell. It removes pretty much all gstreamer code from pithos.py. The proxy support and auto plugin install still needs tested.