1
# Copyright 2004 Joe Wreschnig, Michael Urman
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License version 2 as
5
# published by the Free Software Foundation
7
# $Id: gstbe.py 4330 2008-09-14 03:19:26Z piman $
16
from quodlibet import config
17
from quodlibet import const
19
from quodlibet.util import fver
20
from quodlibet.player import error as PlayerError
21
from quodlibet.player._base import BasePlayer
23
def GStreamerSink(pipeline):
24
"""Try to create a GStreamer pipeline:
25
* Try making the pipeline (defaulting to gconfaudiosink).
26
* If it fails, fall back to autoaudiosink.
27
* If that fails, complain loudly."""
29
if pipeline == "gconf": pipeline = "gconfaudiosink"
30
try: pipe = gst.parse_launch(pipeline)
31
except gobject.GError, err:
32
if pipeline != "autoaudiosink":
33
try: pipe = gst.parse_launch("autoaudiosink")
34
except gobject.GError: pipe = None
35
else: pipeline = "autoaudiosink"
37
if pipe: return pipe, pipeline
40
_("Unable to create audio output"),
41
_("The audio output pipeline %r could not be created. Check "
42
"your GStreamer settings in ~/.quodlibet/config.") % pipeline)
44
class GStreamerPlayer(BasePlayer):
45
__gproperties__ = BasePlayer._gproperties_
46
__gsignals__ = BasePlayer._gsignals_
48
def __init__(self, sinkname, librarian=None):
49
super(GStreamerPlayer, self).__init__()
50
device, sinkname = GStreamerSink(sinkname)
52
self.version_info = "GStreamer: %s / PyGSt: %s" % (
53
fver(gst.version()), fver(gst.pygst_version))
54
self.bin = gst.element_factory_make('playbin')
55
self.bin.set_property('video-sink', None)
56
self.bin.set_property('audio-sink', device)
57
bus = self.bin.get_bus()
58
bus.add_signal_watch()
59
bus.connect('message', self.__message, librarian)
60
self.connect_object('destroy', self.bin.set_state, gst.STATE_NULL)
63
def __message(self, bus, message, librarian):
64
if message.type == gst.MESSAGE_EOS:
65
self._source.next_ended()
67
elif message.type == gst.MESSAGE_TAG:
68
self.__tag(message.parse_tag(), librarian)
69
elif message.type == gst.MESSAGE_ERROR:
70
err, debug = message.parse_error()
71
err = str(err).decode(const.ENCODING, 'replace')
76
# On GStreamer, we can release the device when stopped.
81
self.bin.set_state(gst.STATE_NULL)
84
def do_set_property(self, property, v):
85
if property.name == 'volume':
88
self.bin.set_property('volume', v)
90
if config.getboolean("player", "replaygain"):
91
profiles = filter(None, self.replaygain_profiles)[0]
92
v = max(0.0, min(4.0, v * self.song.replay_gain(profiles)))
93
self.bin.set_property('volume', v)
97
def get_position(self):
98
"""Return the current playback position in milliseconds,
99
or 0 if no song is playing."""
100
if self.bin.get_property('uri'):
101
try: p = self.bin.query_position(gst.FORMAT_TIME)[0]
102
except gst.QueryError: p = 0
107
def _set_paused(self, paused):
108
if paused != self._paused:
109
self._paused = paused
111
self.emit((paused and 'paused') or 'unpaused')
113
if not self.song.is_file:
114
self.bin.set_state(gst.STATE_NULL)
115
else: self.bin.set_state(gst.STATE_PAUSED)
116
else: self.bin.set_state(gst.STATE_PLAYING)
118
# Something wants us to pause between songs, or when
119
# we've got no song playing (probably StopAfterMenu).
121
def _get_paused(self): return self._paused
122
paused = property(_get_paused, _set_paused)
124
def error(self, message, lock):
125
self.bin.set_property('uri', '')
126
self.bin.set_state(gst.STATE_NULL)
127
self.emit('error', self.song, message, lock)
132
"""Seek to a position in the song, in milliseconds."""
133
if self.bin.get_property('uri'):
134
pos = max(0, int(pos))
135
if pos >= self._length:
139
gst_time = pos * gst.MSECOND
140
event = gst.event_new_seek(
141
1.0, gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH,
142
gst.SEEK_TYPE_SET, gst_time, gst.SEEK_TYPE_NONE, 0)
143
if self.bin.send_event(event):
144
self.emit('seek', self.song, pos)
146
def _end(self, stopped):
147
# We need to set self.song to None before calling our signal
148
# handlers. Otherwise, if they try to end the song they're given
149
# (e.g. by removing it), then we get in an infinite loop.
151
self.song = self.info = None
152
self.emit('song-ended', song, stopped)
154
# Then, set up the next song.
155
self.song = self.info = self._source.current
156
self.emit('song-started', self.song)
158
if self.song is not None:
159
# Changing the URI in a playbin requires "resetting" it.
160
if not self.bin.set_state(gst.STATE_NULL): return
161
self.bin.set_property('uri', self.song("~uri"))
162
self._length = self.song["~#length"] * 1000
163
if self._paused: self.bin.set_state(gst.STATE_PAUSED)
164
else: self.bin.set_state(gst.STATE_PLAYING)
168
self.bin.set_state(gst.STATE_NULL)
169
self.bin.set_property('uri', '')
171
def __tag(self, tags, librarian):
172
if self.song and self.song.multisong:
173
self._fill_stream(tags, librarian)
174
elif self.song and self.song.fill_metadata:
177
def _fill_stream(self, tags, librarian):
180
if self.info is self.song:
181
self.info = type(self.song)(self.song["~filename"])
182
self.info.multisong = False
184
for k in tags.keys():
185
value = str(tags[k]).strip()
186
if not value: continue
188
try: bitrate = int(value)
189
except (ValueError, TypeError): pass
191
if bitrate != self.song.get("~#bitrate"):
193
self.song["~#bitrate"] = bitrate
194
self.info["~#bitrate"] = bitrate
195
elif k == "duration":
196
try: length = int(long(value) / gst.SECOND)
197
except (ValueError, TypeError): pass
199
if length != self.song.get("~#length"):
201
self.info["~#length"] = length
202
elif k in ["emphasis", "mode", "layer"]:
204
elif isinstance(value, basestring):
205
value = unicode(value, errors='replace')
206
k = {"track-number": "tracknumber",
207
"location": "website"}.get(k, k)
208
if self.info.get(k) == value:
214
self.song[k] = self.info[k] = value
218
self.emit('song-started', self.info)
219
elif changed and librarian is not None:
220
librarian.changed([self.song])
222
def can_play_uri(uri):
223
return gst.element_make_from_uri(gst.URI_SRC, uri, '') is not None
226
pipeline = config.get("player", "gst_pipeline") or "gconfaudiosink"
227
gst.debug_set_default_threshold(gst.LEVEL_ERROR)
228
if gst.element_make_from_uri(
230
"file:///fake/path/for/gst", ""):
231
return GStreamerPlayer(pipeline, librarian)
234
_("Unable to open input files"),
235
_("GStreamer has no element to handle reading files. Check "
236
"your GStreamer installation settings."))