Esempio n. 1
0
    def __handle_missing_plugin(self, message):
        get_installer_detail = \
            GstPbutils.missing_plugin_message_get_installer_detail
        get_description = GstPbutils.missing_plugin_message_get_description

        details = get_installer_detail(message)
        if details is None:
            return

        self.stop()

        format_desc = get_description(message)
        title = _(u"No GStreamer element found to handle media format")
        error_details = _(u"Media format: %(format-description)s") % {
            "format-description": format_desc.decode("utf-8")
        }

        def install_done_cb(plugins_return, *args):
            print_d("Gstreamer plugin install return: %r" % plugins_return)
            Gst.update_registry()

        context = GstPbutils.InstallPluginsContext.new()
        res = GstPbutils.install_plugins_async([details], context,
                                               install_done_cb, None)
        print_d("Gstreamer plugin install result: %r" % res)

        if res in (GstPbutils.InstallPluginsReturn.HELPER_MISSING,
                   GstPbutils.InstallPluginsReturn.INTERNAL_FAILURE):
            self._error(PlayerError(title, error_details))
Esempio n. 2
0
def GStreamerSink(pipeline_desc):
    """Returns a list of unlinked gstreamer elements ending with an audio sink
    and a textual description of the pipeline.

    `pipeline_desc` can be gst-launch syntax for multiple elements
    with or without an audiosink.

    In case of an error, raises PlayerError
    """

    pipe = None
    if pipeline_desc:
        try:
            pipe = [Gst.parse_launch(e) for e in pipeline_desc.split('!')]
        except GLib.GError as e:
            message = e.message.decode("utf-8")
            raise PlayerError(_("Invalid GStreamer output pipeline"), message)

    if pipe:
        # In case the last element is linkable with a fakesink
        # it is not an audiosink, so we append the default one
        fake = Gst.ElementFactory.make('fakesink', None)
        if link_many([pipe[-1], fake]):
            unlink_many([pipe[-1], fake])
            default_elm, default_desc = find_audio_sink()
            pipe += [default_elm]
            pipeline_desc += " ! " + default_desc
    else:
        elm, pipeline_desc = find_audio_sink()
        pipe = [elm]

    return pipe, pipeline_desc
Esempio n. 3
0
def find_audio_sink():
    """Get the best audio sink available.

    Returns (element, description) or raises PlayerError.
    """

    if is_windows():
        sinks = [
            "directsoundsink",
            "autoaudiosink",
        ]
    elif is_linux() and pulse_is_running():
        sinks = [
            "pulsesink",
        ]
    else:
        sinks = [
            "autoaudiosink",  # plugins-good
            "pulsesink",  # plugins-good
            "alsasink",  # plugins-base
        ]

    for name in sinks:
        element = Gst.ElementFactory.make(name, None)
        if element is not None:
            return (element, name)
    else:
        details = " (%s)" % ", ".join(sinks) if sinks else ""
        raise PlayerError(_("No GStreamer audio sink found") + details)
Esempio n. 4
0
def find_audio_sink() -> Tuple[Gst.Element, str]:
    """Get the best audio sink available.

    Returns (element, description) or raises PlayerError.
    """
    def sink_options():
        # People with Jack running probably want it more than any other options
        if config.getboolean("player", "gst_use_jack") and jack_is_running():
            print_d("Using JACK output via Gstreamer")
            return [AudioSinks.JACK]
        elif is_windows():
            return [AudioSinks.DIRECTSOUND]
        elif is_linux() and pulse_is_running():
            return [AudioSinks.PULSE]
        else:
            return [
                AudioSinks.AUTO,
                AudioSinks.PULSE,
                AudioSinks.ALSA,
            ]

    options = sink_options()
    for sink in options:
        element = Gst.ElementFactory.make(sink.value, "player")
        if (sink == AudioSinks.JACK
                and not config.getboolean("player", "gst_jack_auto_connect")):
            # Disable the auto-connection to outputs (e.g. maybe there's scripting)
            element.set_property("connect", "none")
        if element is not None:
            return element, sink.value
    else:
        details = ', '.join(s.value for s in options) if options else "[]"
        raise PlayerError(
            _("No GStreamer audio sink found. Tried: %s") % details)
Esempio n. 5
0
    def __message(self, bus, message, librarian):
        if message.type == Gst.MessageType.EOS:
            print_d("Stream EOS")
            if not self._in_gapless_transition:
                self._source.next_ended()
            self._end(False)
        elif message.type == Gst.MessageType.TAG:
            self.__tag(message.parse_tag(), librarian)
        elif message.type == Gst.MessageType.ERROR:
            gerror, debug_info = message.parse_error()
            message = u""
            if gerror:
                message = gerror.message.decode("utf-8").rstrip(".")
            details = None
            if debug_info:
                # strip the first line, not user friendly
                debug_info = "\n".join(debug_info.splitlines()[1:])
                # can contain paths, so not sure if utf-8 in all cases
                details = debug_info.decode("utf-8", errors="replace")
            self._error(PlayerError(message, details))
        elif message.type == Gst.MessageType.STATE_CHANGED:
            # pulsesink doesn't notify a volume change on startup
            # and the volume is only valid in > paused states.
            if message.src is self._ext_vol_element:
                self.notify("volume")
        elif message.type == Gst.MessageType.STREAM_START:
            if self._in_gapless_transition:
                print_d("Stream changed")
                self._end(False)
        elif message.type == Gst.MessageType.ASYNC_DONE:
            if self._active_seeks:
                song, pos = self._active_seeks.pop(0)
                if song is self.song:
                    self.emit("seek", song, pos)
        elif message.type == Gst.MessageType.ELEMENT:
            message_name = message.get_structure().get_name()

            if message_name == "missing-plugin":
                self.__handle_missing_plugin(message)
        elif message.type == Gst.MessageType.CLOCK_LOST:
            print_d("Clock lost")
            self.bin.set_state(Gst.State.PAUSED)
            self.bin.set_state(Gst.State.PLAYING)
        elif message.type == Gst.MessageType.LATENCY:
            print_d("Recalculate latency")
            self.bin.recalculate_latency()
        elif message.type == Gst.MessageType.REQUEST_STATE:
            state = message.parse_request_state()
            print_d("State requested: %s" % Gst.Element.state_get_name(state))
            self.bin.set_state(state)
        elif message.type == Gst.MessageType.DURATION_CHANGED:
            if self.song.fill_length:
                ok, p = self.bin.query_duration(Gst.Format.TIME)
                if ok:
                    p /= float(Gst.SECOND)
                    self.song["~#length"] = p
                    librarian.changed([self.song])
Esempio n. 6
0
def init(librarian):
    # Enable error messages by default
    if Gst.debug_get_default_threshold() == Gst.DebugLevel.NONE:
        Gst.debug_set_default_threshold(Gst.DebugLevel.ERROR)

    if Gst.Element.make_from_uri(Gst.URIType.SRC, "file:///fake/path/for/gst",
                                 ""):
        return GStreamerPlayer(librarian)
    else:
        raise PlayerError(
            _("Unable to open input files"),
            _("GStreamer has no element to handle reading files. Check "
              "your GStreamer installation settings."))
Esempio n. 7
0
    def __handle_missing_plugin(self, message):
        get_installer_detail = \
            GstPbutils.missing_plugin_message_get_installer_detail
        get_description = GstPbutils.missing_plugin_message_get_description

        details = get_installer_detail(message)
        if details is None:
            return

        self.stop()

        format_desc = get_description(message)
        title = _(u"No GStreamer element found to handle media format")
        error_details = _(u"Media format: %(format-description)s") % {
            "format-description": util.gdecode(format_desc)
        }

        def install_done_cb(plugins_return, *args):
            print_d("Gstreamer plugin install return: %r" % plugins_return)
            Gst.update_registry()

        context = GstPbutils.InstallPluginsContext.new()

        # new in 1.6
        if hasattr(context, "set_desktop_id"):
            from gi.repository import Gtk
            context.set_desktop_id(app.id)

        # new in 1.6
        if hasattr(context, "set_startup_notification_id"):
            current_time = Gtk.get_current_event_time()
            context.set_startup_notification_id("_TIME%d" % current_time)

        gdk_window = app.window.get_window()
        if gdk_window:
            try:
                xid = gdk_window.get_xid()
            except AttributeError:  # non X11
                pass
            else:
                context.set_xid(xid)

        res = GstPbutils.install_plugins_async([details], context,
                                               install_done_cb, None)
        print_d("Gstreamer plugin install result: %r" % res)

        if res in (GstPbutils.InstallPluginsReturn.HELPER_MISSING,
                   GstPbutils.InstallPluginsReturn.INTERNAL_FAILURE):
            self._error(PlayerError(title, error_details))
Esempio n. 8
0
 def _event_listener(self, user_data, event):
     event = event.contents
     if event.type == XINE_EVENT_UI_PLAYBACK_FINISHED:
         GLib.idle_add(self._playback_finished, priority=GLib.PRIORITY_HIGH)
     elif event.type == XINE_EVENT_UI_SET_TITLE:
         GLib.idle_add(self._update_metadata, priority=GLib.PRIORITY_HIGH)
     elif event.type == XINE_EVENT_UI_MESSAGE:
         from ctypes import POINTER, cast, string_at, addressof
         msg = cast(event.data, POINTER(xine_ui_message_data_t)).contents
         if msg.type != XINE_MSG_NO_ERROR:
             if msg.explanation:
                 message = string_at(addressof(msg) + msg.explanation)
             else:
                 message = "xine error %s" % msg.type
             message = message.decode("utf-8", errors="replace")
             GLib.idle_add(self._error, PlayerError(message))
     return True
Esempio n. 9
0
 def _new_stream(self, driver):
     self._audio_port = self._handle.open_audio_driver(driver, None)
     if not self._audio_port:
         raise PlayerError(
             _("Unable to create audio output"),
             _("The audio device %r was not found. Check your Xine "
               "settings in ~/.quodlibet/config.") % driver)
     self._stream = self._handle.stream_new(self._audio_port, None)
     xine_set_param(self._stream, XINE_PARAM_IGNORE_VIDEO, 1)
     xine_set_param(self._stream, XINE_PARAM_IGNORE_SPU, 1)
     self.update_eq_values()
     if self._supports_gapless:
         xine_set_param(self._stream, XINE_PARAM_EARLY_FINISHED_EVENT, 1)
     if self._event_queue:
         xine_event_dispose_queue(self._event_queue)
     self._event_queue = xine_event_new_queue(self._stream)
     xine_event_create_listener_thread(self._event_queue,
                                       self._event_listener, None)
Esempio n. 10
0
    def __message(self, bus, message, librarian):
        if message.type == Gst.MessageType.EOS:
            print_d("Stream EOS")
            if not self._in_gapless_transition:
                self._source.next_ended()
            self._end(False)
        elif message.type == Gst.MessageType.TAG:
            self.__tag(message.parse_tag(), librarian)
        elif message.type == Gst.MessageType.ERROR:
            gerror, debug_info = message.parse_error()
            message = u""
            if gerror:
                message = gerror.message.decode("utf-8").rstrip(".")
            details = None
            if debug_info:
                # strip the first line, not user friendly
                debug_info = "\n".join(debug_info.splitlines()[1:])
                # can contain paths, so not sure if utf-8 in all cases
                details = debug_info.decode("utf-8", errors="replace")
            self._error(PlayerError(message, details))

        elif message.type == Gst.MessageType.STREAM_START:
            if self._in_gapless_transition:
                print_d("Stream changed")
                self._end(False)
        elif message.type == Gst.MessageType.ASYNC_DONE:
            if self._active_seeks:
                song, pos = self._active_seeks.pop(0)
                if song is self.song:
                    self.emit("seek", song, pos)
        elif message.type == Gst.MessageType.ELEMENT:
            message_name = message.get_structure().get_name()

            if message_name == "missing-plugin":
                self.__handle_missing_plugin(message)

        return True
Esempio n. 11
0
def find_audio_sink():
    """Get the best audio sink available.

    Returns (element, description) or raises PlayerError.
    """

    if os.name == "nt":
        sinks = [
            "directsoundsink",
            "autoaudiosink",
        ]
    else:
        sinks = [
            "autoaudiosink",  # plugins-good
            "pulsesink",  # plugins-good
            "alsasink",  # plugins-base
        ]

    for name in sinks:
        element = Gst.ElementFactory.make(name, None)
        if element is not None:
            return (element, name)
    else:
        raise PlayerError(_("No GStreamer audio sink found"))
Esempio n. 12
0
 def _error(self, message):
     self.paused = True
     self.emit('error', self.song, PlayerError(message))
Esempio n. 13
0
    def __init_pipeline(self):
        """Creates a gstreamer pipeline. Returns True on success."""

        if self.bin:
            return True

        # reset error state
        self.error = False

        pipeline = config.get("player", "gst_pipeline")
        try:
            pipeline, self._pipeline_desc = GStreamerSink(pipeline)
        except PlayerError as e:
            self._error(e)
            return False

        if self._use_eq and Gst.ElementFactory.find('equalizer-10bands'):
            # The equalizer only operates on 16-bit ints or floats, and
            # will only pass these types through even when inactive.
            # We push floats through to this point, then let the second
            # audioconvert handle pushing to whatever the rest of the
            # pipeline supports. As a bonus, this seems to automatically
            # select the highest-precision format supported by the
            # rest of the chain.
            filt = Gst.ElementFactory.make('capsfilter', None)
            filt.set_property('caps',
                              Gst.Caps.from_string('audio/x-raw,format=F32LE'))
            eq = Gst.ElementFactory.make('equalizer-10bands', None)
            self._eq_element = eq
            self.update_eq_values()
            conv = Gst.ElementFactory.make('audioconvert', None)
            resample = Gst.ElementFactory.make('audioresample', None)
            pipeline = [filt, eq, conv, resample] + pipeline

        # playbin2 has started to control the volume through pulseaudio,
        # which means the volume property can change without us noticing.
        # Use our own volume element for now until this works with PA.
        self._int_vol_element = Gst.ElementFactory.make('volume', None)
        pipeline.insert(0, self._int_vol_element)

        # Get all plugin elements and append audio converters.
        # playbin already includes one at the end
        plugin_pipeline = []
        for plugin in self._get_plugin_elements():
            plugin_pipeline.append(plugin)
            plugin_pipeline.append(
                Gst.ElementFactory.make('audioconvert', None))
            plugin_pipeline.append(
                Gst.ElementFactory.make('audioresample', None))
        pipeline = plugin_pipeline + pipeline

        bufbin = Gst.Bin()
        for element in pipeline:
            assert element is not None, pipeline
            bufbin.add(element)

        if len(pipeline) > 1:
            if not link_many(pipeline):
                print_w("Linking the GStreamer pipeline failed")
                self._error(
                    PlayerError(_("Could not create GStreamer pipeline")))
                return False

        # see if the sink provides a volume property, if yes, use it
        sink_element = pipeline[-1]
        if isinstance(sink_element, Gst.Bin):
            sink_element = iter_to_list(sink_element.iterate_recurse)[-1]

        self._ext_vol_element = None
        if hasattr(sink_element.props, "volume"):
            self._ext_vol_element = sink_element

            # In case we use the sink volume directly we can increase buffering
            # without affecting the volume change delay too much and safe some
            # CPU time... (2x default for now).
            if hasattr(sink_element.props, "buffer_time"):
                sink_element.set_property("buffer-time", 400000)

            def ext_volume_notify(*args):
                # gets called from a thread
                GLib.idle_add(self.notify, "volume")

            self._ext_vol_element.connect("notify::volume", ext_volume_notify)

        self._ext_mute_element = None
        if hasattr(sink_element.props, "mute") and \
                sink_element.get_factory().get_name() != "directsoundsink":
            # directsoundsink has a mute property but it doesn't work
            # https://bugzilla.gnome.org/show_bug.cgi?id=755106
            self._ext_mute_element = sink_element

            def mute_notify(*args):
                # gets called from a thread
                GLib.idle_add(self.notify, "mute")

            self._ext_mute_element.connect("notify::mute", mute_notify)

        # Make the sink of the first element the sink of the bin
        gpad = Gst.GhostPad.new('sink', pipeline[0].get_static_pad('sink'))
        bufbin.add_pad(gpad)

        bin_ = Gst.ElementFactory.make('playbin', None)
        assert bin_

        self.bin = BufferingWrapper(bin_, self)
        self._seeker = Seeker(self.bin, self)

        bus = bin_.get_bus()
        bus.add_signal_watch()
        self.__bus_id = bus.connect('message', self.__message, self._librarian)

        self.__atf_id = self.bin.connect('about-to-finish',
                                         self.__about_to_finish)

        # set buffer duration
        duration = config.getfloat("player", "gst_buffer")
        self._set_buffer_duration(int(duration * 1000))

        # connect playbin to our pluing/volume/eq pipeline
        self.bin.set_property('audio-sink', bufbin)

        # by default playbin will render video -> suppress using fakesink
        fakesink = Gst.ElementFactory.make('fakesink', None)
        self.bin.set_property('video-sink', fakesink)

        # disable all video/text decoding in playbin
        GST_PLAY_FLAG_VIDEO = 1 << 0
        GST_PLAY_FLAG_TEXT = 1 << 2
        flags = self.bin.get_property("flags")
        flags &= ~(GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_TEXT)
        self.bin.set_property("flags", flags)

        # find the (uri)decodebin after setup and use autoplug-sort
        # to sort elements like decoders
        def source_setup(*args):
            def autoplug_sort(decode, pad, caps, factories):
                def set_prio(x):
                    i, f = x
                    i = {"mad": -1, "mpg123audiodec": -2}.get(f.get_name(), i)
                    return (i, f)

                return list(
                    zip(*sorted(map(set_prio, enumerate(factories)))))[1]

            for e in iter_to_list(self.bin.iterate_recurse):
                try:
                    e.connect("autoplug-sort", autoplug_sort)
                except TypeError:
                    pass
                else:
                    break

        self.bin.connect("source-setup", source_setup)

        if not self.has_external_volume:
            # Restore volume/ReplayGain and mute state
            self.volume = self._volume
            self.mute = self._mute

        # ReplayGain information gets lost when destroying
        self._reset_replaygain()

        if self.song:
            self.bin.set_property('uri', self.song("~uri"))

        return True
Esempio n. 14
0
    def __init_pipeline(self):
        """Creates a gstreamer pipeline. Returns True on success."""

        if self.bin:
            return True

        # reset error state
        self.error = False

        pipeline = config.get("player", "gst_pipeline")
        try:
            pipeline, self._pipeline_desc = GStreamerSink(pipeline)
        except PlayerError as e:
            self._error(e)
            return False

        if self._use_eq and Gst.ElementFactory.find('equalizer-10bands'):
            # The equalizer only operates on 16-bit ints or floats, and
            # will only pass these types through even when inactive.
            # We push floats through to this point, then let the second
            # audioconvert handle pushing to whatever the rest of the
            # pipeline supports. As a bonus, this seems to automatically
            # select the highest-precision format supported by the
            # rest of the chain.
            filt = Gst.ElementFactory.make('capsfilter', None)
            filt.set_property('caps',
                              Gst.Caps.from_string('audio/x-raw,format=F32LE'))
            eq = Gst.ElementFactory.make('equalizer-10bands', None)
            self._eq_element = eq
            self.update_eq_values()
            conv = Gst.ElementFactory.make('audioconvert', None)
            resample = Gst.ElementFactory.make('audioresample', None)
            pipeline = [filt, eq, conv, resample] + pipeline

        # playbin2 has started to control the volume through pulseaudio,
        # which means the volume property can change without us noticing.
        # Use our own volume element for now until this works with PA.
        self._vol_element = Gst.ElementFactory.make('volume', None)
        pipeline.insert(0, self._vol_element)

        # Get all plugin elements and append audio converters.
        # playbin already includes one at the end
        plugin_pipeline = []
        for plugin in self._get_plugin_elements():
            plugin_pipeline.append(plugin)
            plugin_pipeline.append(
                Gst.ElementFactory.make('audioconvert', None))
            plugin_pipeline.append(
                Gst.ElementFactory.make('audioresample', None))
        pipeline = plugin_pipeline + pipeline

        bufbin = Gst.Bin()
        for element in pipeline:
            assert element is not None, pipeline
            bufbin.add(element)

        PIPELINE_ERROR = PlayerError(_("Could not create GStreamer pipeline"))

        if len(pipeline) > 1:
            if not link_many(pipeline):
                print_w("Linking the GStreamer pipeline failed")
                self._error(PIPELINE_ERROR)
                return False

        # Test to ensure output pipeline can preroll
        bufbin.set_state(Gst.State.READY)
        result, state, pending = bufbin.get_state(timeout=STATE_CHANGE_TIMEOUT)
        if result == Gst.StateChangeReturn.FAILURE:
            bufbin.set_state(Gst.State.NULL)
            print_w("Prerolling the GStreamer pipeline failed")
            self._error(PIPELINE_ERROR)
            return False

        # Make the sink of the first element the sink of the bin
        gpad = Gst.GhostPad.new('sink', pipeline[0].get_static_pad('sink'))
        bufbin.add_pad(gpad)

        self.bin = Gst.ElementFactory.make('playbin', None)
        assert self.bin

        bus = self.bin.get_bus()
        bus.add_signal_watch()
        self.__bus_id = bus.connect('message', self.__message, self._librarian)

        self.bin = BufferingWrapper(self.bin, self)
        self.__atf_id = self.bin.connect('about-to-finish',
                                         self.__about_to_finish)

        # set buffer duration
        duration = config.getfloat("player", "gst_buffer")
        self._set_buffer_duration(int(duration * 1000))

        # connect playbin to our pluing/volume/eq pipeline
        self.bin.set_property('audio-sink', bufbin)

        # by default playbin will render video -> suppress using fakesink
        fakesink = Gst.ElementFactory.make('fakesink', None)
        self.bin.set_property('video-sink', fakesink)

        # disable all video/text decoding in playbin
        GST_PLAY_FLAG_VIDEO = 1 << 0
        GST_PLAY_FLAG_TEXT = 1 << 2
        flags = self.bin.get_property("flags")
        flags &= ~(GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_TEXT)
        self.bin.set_property("flags", flags)

        # find the (uri)decodebin after setup and use autoplug-sort
        # to sort elements like decoders
        def source_setup(*args):
            def autoplug_sort(decode, pad, caps, factories):
                def set_prio(x):
                    i, f = x
                    i = {"mad": -1, "mpg123audiodec": -2}.get(f.get_name(), i)
                    return (i, f)

                return zip(*sorted(map(set_prio, enumerate(factories))))[1]

            for e in iter_to_list(self.bin.iterate_recurse):
                try:
                    e.connect("autoplug-sort", autoplug_sort)
                except TypeError:
                    pass
                else:
                    break

        self.__source_setup_id = self.bin.connect("source-setup", source_setup)

        # ReplayGain information gets lost when destroying
        self.volume = self.volume

        if self.song:
            self.bin.set_property('uri', self.song("~uri"))

        return True