[gnome-music/wip/merge: 10/343] write player.py
- From: Ignacio Casal Quinteiro <icq src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-music/wip/merge: 10/343] write player.py
- Date: Thu, 25 Jul 2013 11:14:21 +0000 (UTC)
commit d6ce8c6b31607200dafa56255a534f3a2d912ba7
Author: Eslam Mostafa <cseslam gmail com>
Date: Sat Jul 13 03:00:50 2013 +0200
write player.py
gnome-music/player.py | 617 +++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 617 insertions(+), 0 deletions(-)
---
diff --git a/gnome-music/player.py b/gnome-music/player.py
new file mode 100644
index 0000000..cb49472
--- /dev/null
+++ b/gnome-music/player.py
@@ -0,0 +1,617 @@
+from gi.repository import Gtk, Gst, GLib
+
+ART_SIZE = 34
+
+class Player():
+ def __init__(self):
+ self.playlist = None
+ self.playlistType = None
+ self.playlistId = None
+ self.playlistField = None
+ self.currentTrack = None
+ self._lastState = Gst.State.PAUSED
+ self.cache = AlbumArtCache.getDefault()
+ self._symbolicIcon = self.cache.makeDefaultIcon(ART_SIZE, ART_SIZE)
+
+ Gst.init(None, 0)
+ self.discoverer = new GstPbutils.Discoverer()
+ self.player = Gst.ElementFactory.make("playbin", "player")
+ self.bus = self.player.get_bus()
+ self.bus.add_signal_watch()
+
+ self._settings = Gio.Settings.new('org.gnome.Music')
+ self._settings.connect('changed::repeat', self._onSettingsChanged)
+ self.repeat = self._settings.get_enum('repeat')
+
+ self._dbusImpl = Gio.DBusExportedObject.wrapJSObject(MediaPlayer2PlayerIface, self)
+ self._dbusImpl.export(Gio.DBus.session, '/org/mpris/MediaPlayer2')
+
+ self.bus.connect("message::state-changed", self._onBusStateChanged)
+ self.bus.connect("message::error", self._onBusError)
+ self.bus.connect("message::eos", self._onBusEos)
+
+ if(nextTrack):
+ GLib.idle_add(GLib.PRIORITY_HIGH, self._onGLibIdle)
+ elif (self.repeat == RepeatType.NONE):
+ self.stop()
+ self.playBtn.set_image(self._playImage)
+ self.progressScale.set_value(0)
+ self.progressScale.sensitive = False
+ self.currentTrack = self.playlist.get_iter_first()[1]
+ self.load(self.playlist.get_value(self.currentTrack, self.playlistField))
+ else:
+ #Stop playback
+ self.stop()
+ self.playBtn.set_image(self._playImage)
+ self.progressScale.set_value(0)
+ self.progressScale.sensitive = False
+
+ self._setupView()
+
+
+ def _onSettingsChanged(self):
+ self.repeat = settings.get_enum('repeat')
+ self._syncPrevNext()
+ self._syncRepeatImage()
+
+ def _onBusStateChanged(self, bus, message):
+ #Note: not all state changes are signaled through here, in particular
+ #transitions between Gst.State.READY and Gst.State.NULL are never async
+ #and thus don't cause a message
+ #In practice, self means only Gst.State.PLAYING and Gst.State.PAUSED are
+ self._syncPlaying()
+ self.emit('playing-changed')
+
+ def _onBussError(self, bus, message):
+ let media = self.playlist.get_value( self.currentTrack, self.playlistField)
+ if(media != None):
+ uri = media.get_url()
+ else:
+ uri = "none"
+ log("URI:" + uri)
+ log("Error:" + message.parse_error())
+ self.stop()
+ return True
+
+ def _onBusEos(self, bus, message):
+ nextTrack = self._getNextTrack()
+
+ def _onGLibIdle(self):
+ self.currentTrack = nextTrack
+ self.play()
+
+
+ def _getNextTrack(self):
+ currentTrack = self.currentTrack
+ nextTrack = None
+ if self.repeat == RepeatType.SONG:
+ nextTrack = currentTrack
+ elif self.repeat == RepeatType.ALL:
+ nextTrack = currentTrack.copy()
+ if !self.playlist.iter_next(nextTrack):
+ nextTrack = self.playlist.get_iter_first()[1]
+ elif self.repeat == RepeatType.NONE:
+ nextTrack = currentTrack.copy()
+ nextTrack = self.playlist.iter_next(nextTrack) ? nextTrack : None
+ elif self.repeat == RepeatType.SHUFFLE:
+ nextTrack = self.playlist.get_iter_first()[1]
+ let rows = self.playlist.iter_n_children(null)
+ let random = Math.floor(Math.random() * rows)
+ for i in random:
+ self.playlist.iter_next(nextTrack)
+
+ return nextTrack
+
+ def _getIterLast():
+ ok, iter = self.playlist.get_iter_first()
+ last = None
+
+ while(ok):
+ last = iter.copy()
+ ok = self.playlist.iter_next(iter)
+
+ return last
+
+
+ def _getPreviousTrack(self):
+ let currentTrack = self.currentTrack
+ let previousTrack
+
+ if self.repeat = RepeatType.SONG:
+ previousTrack = currentTrack
+ elif self.repeat = RepeatType.ALL:
+ previousTrack = currentTrack.copy()
+ if !self.playlist.iter_previous(previousTrack):
+ previousTrack = self._getIterLast()
+ elif self.repeat = RepeatType.NONE:
+ previousTrack = currentTrack.copy()
+ previousTrack = self.playlist.iter_previous(previousTrack) ? previousTrack : null
+ elif self.repeat = RepeatType.SHUFFLE:
+ previousTrack = self.playlist.get_iter_first()[1]
+ rows = self.playlist.iter_n_children(null)
+ random = Math.floor(Math.random() * rows)
+ for i in random:
+ self.playlist.iter_next(previousTrack)
+
+ return previousTrack
+
+
+ def _hasNext(self):
+ if self.repeat == RepeatType.ALL or
+ self.repeat == RepeatType.SONG or
+ self.repeat == RepeatType.SHUFFLE:
+ return True
+ else:
+ tmp = self.currentTrack.copy()
+ return self.playlist.iter_next(tmp)
+
+ def _hasPrevious(self):
+ if self.repeat == RepeatType.ALL or
+ self.repeat == RepeatType.SONG or
+ self.repeat == RepeatType.SHUFFLE:
+ return True
+ else:
+ tmp = self.currentTrack.copy()
+ return self.playlist.iter_previous(tmp)
+
+ def getPlaying(self):
+ ok, state, pending = self.player.get_state(0)
+ #log('get playing(), [ok, state, pending] = [%s, %s, %s]'.format(ok, state, pending))
+ if ok == Gst.StateChangeReturn.ASYNC:
+ return pending == Gst.State.PLAYING
+ elif ok == Gst.StateChangeReturn.SUCCESS:
+ return state == Gst.State.PLAYING
+ else:
+ return False
+
+ def _syncPlaying(self):
+ self.playBtn.image = self.playing ? self._pauseImage : self._playImage
+
+ def _syncPrevNext(self):
+ hasNext = self._hasNext()
+ hasPrevious = self._hasPrevious()
+
+ self.nextBtn.sensitive = hasNext
+ self.prevBtn.sensitive = hasPrevious
+
+ self._dbusImpl.emit_property_changed('CanGoNext', GLib.Variant.new('b', hasNext))
+ self._dbusImpl.emit_property_changed('CanGoPrevious', GLib.Variant.new('b', hasPrevious))
+ },
+
+ def setPlaying(self, value):
+ self.eventBox.show()
+
+ if (value)
+ self.play()
+ else
+ self.pause()
+
+ media = self.playlist.get_value(self.currentTrack, self.playlistField)
+ self.playBtn.set_image(self._pauseImage)
+
+ def load(self, media):
+ self._setDuration(media.get_duration())
+ self.songTotalTimeLabel.label = self.secondsToString(media.get_duration())
+ self.progressScale.sensitive = true
+
+ self.playBtn.sensitive = True
+ self._syncPrevNext()
+
+ self.coverImg.set_from_pixbuf(self._symbolicIcon)
+ self.cache.lookup(ART_SIZE, media.get_artist(), media.get_string(Grl.METADATA_KEY_ALBUM),
_onCacheLookup)
+
+ if media.get_title() != None:
+ self.titleLabel.set_label(media.get_title())
+ else:
+ url = media.get_url(),
+ filename = GLib.File.new_for_path(url),
+ basename = filename.get_basename(),
+ toShow = GLib.Uri.unescape_string(basename, None)
+ self.titleLabel.set_label(toShow)
+
+ if media.get_artist() != None:
+ self.artistLabel.set_label(media.get_artist())
+ else:
+ self.artistLabel.set_label("Unknown artist")
+
+ url = media.get_url()
+ if url != self.player.current_uri:
+ self.player.uri = url
+
+ #Store next available url
+ #(not really useful because we can't connect to about-to-finish, but still)
+ nextTrack = self._getNextTrack()
+
+ if nextTrack:
+ nextMedia = self.playlist.get_value(self.currentTrack, self.playlistField)
+ self.player.nextUrl = nextMedia.get_url()
+ else:
+ self.player.nextUrl = None
+
+ self._dbusImpl.emit_property_changed('Metadata', GLib.Variant.new('a{sv}', self.Metadata))
+ self._dbusImpl.emit_property_changed('CanPlay', GLib.Variant.new('b', true))
+ self._dbusImpl.emit_property_changed('CanPause', GLib.Variant.new('b', true))
+
+ self.emit("playlist-item-changed", self.playlist, self.currentTrack)
+ self.emit('current-changed')
+
+ def _onCacheLookup(self, pixbuf):
+ if (pixbuf != None) {
+ self.coverImg.set_from_pixbuf(pixbuf)
+
+ def play(self):
+ if (self.playlist == None)
+ return True
+
+ if self.player.get_state(1)[1] != Gst.State.PAUSED:
+ self.stop()
+
+ self.load(self.playlist.get_value(self.currentTrack, self.playlistField))
+
+ self.player.set_state(Gst.State.PLAYING)
+ self._updatePositionCallback()
+ if (!self.timeout)
+ self.timeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, self._updatePositionCallback)
+
+ self._dbusImpl.emit_property_changed('PlaybackStatus', GLib.Variant.new('s', 'Playing'))
+
+ def pause(self):
+ if self.timeout:
+ GLib.source_remove(self.timeout)
+ self.timeout = 0
+
+ self.player.set_state(Gst.State.PAUSED)
+ self._dbusImpl.emit_property_changed('PlaybackStatus', GLib.Variant.new('s', 'Paused'))
+
+ def stop(self):
+ if self.timeout:
+ GLib.source_remove(self.timeout)
+ self.timeout = 0
+
+ self.player.set_state(Gst.State.NULL)
+ self._dbusImpl.emit_property_changed('PlaybackStatus', GLib.Variant.new('s', 'Stopped'))
+ self.emit('playing-changed')
+
+ def playNext(self):
+ if self.playlist == None:
+ return True
+
+ if !self.nextBtn.sensitive:
+ return True
+
+ self.stop()
+ self.currentTrack = self._getNextTrack()
+
+ if self.currentTrack:
+ self.play()
+
+ def playPrevious(self):
+ if self.playlist == None:
+ return True
+
+ if !self.prevBtn.sensitive:
+ return True
+
+ self.stop()
+ self.currentTrack = self._getPreviousTrack()
+
+ if self.currentTrack:
+ self.play()
+
+ def setPlaylist(self, type, id, model, iter, field):
+ self.stop()
+
+ self.playlist = model
+ self.playlistType = type
+ self.playlistId = id
+ self.currentTrack = iter
+ self.playlistField = field
+ self.emit('current-changed')
+
+ def runningPlaylist(self, type, id, force){
+ if type == self.playlistType && id == self.playlistId:
+ return self.playlist
+ else:
+ return None
+
+ def _setupView(self):
+ self._ui = new Gtk.Builder()
+ self._ui.add_from_resource('/org/gnome/music/PlayerToolbar.ui')
+ self.eventBox = self._ui.get_object('eventBox')
+ self.prevBtn = self._ui.get_object('previous_button')
+ self.playBtn = self._ui.get_object('play_button')
+ self.nextBtn = self._ui.get_object('next_button')
+ self._playImage = self._ui.get_object('play_image')
+ self._pauseImage = self._ui.get_object('pause_image')
+ self.progressScale = self._ui.get_object('progress_scale')
+ self.songPlaybackTimeLabel = self._ui.get_object('playback')
+ self.songTotalTimeLabel = self._ui.get_object('duration')
+ self.titleLabel = self._ui.get_object('title')
+ self.artistLabel = self._ui.get_object('artist')
+ self.coverImg = self._ui.get_object('cover')
+ self.duration = self._ui.get_object('duration')
+ self.repeatBtnImage = self._ui.get_object('playlistRepeat')
+
+ if Gtk.Settings.get_default().gtk_application_prefer_dark_theme:
+ color = new Gdk.Color(red=65535,green=65535,blue=65535)
+ else:
+ color = new Gdk.Color(red=0,green=0,blue=0)
+ self._playImage.modify_fg(Gtk.StateType.ACTIVE,color)
+ self._pauseImage.modify_fg(Gtk.StateType.ACTIVE,color)
+
+ self._syncRepeatImage()
+
+ self.prevBtn.connect("clicked", self._onPrevBtnClicked)
+ self.playBtn.connect("clicked", self._onPlayBtnClicked)
+ self.nextBtn.connect("clicked", self._onNextBtnClicked)
+ self.progressScale.connect("button-press-event", _onProgrssScaleEvent)
+ self.progressScale.connect("value-changed", Lang.bind(self,
+ function() {
+ let seconds = Math.floor(self.progressScale.get_value() / 60)
+ self.songPlaybackTimeLabel.set_label(self.secondsToString(seconds))
+ return false
+ }))
+ self.progressScale.connect("button-release-event", Lang.bind(self,
+ function() {
+ self.onProgressScaleChangeValue(self.progressScale)
+ self._updatePositionCallback()
+ self.player.set_state(self._lastState)
+ self.timeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, Lang.bind(self,
self._updatePositionCallback))
+ return false
+ }))
+
+ def _onProgressScaleEvent(self):
+ self._lastState = self.player.get_state(1)[1]
+ self.player.set_state(Gst.State.PAUSED)
+ if self.timeout:
+ GLib.source_remove(self.timeout)
+ self.timeout = None
+ return False
+
+ def secondsToString(self, duration):
+ minutes = parseInt( duration / 60 ) % 60
+ seconds = duration % 60
+
+ return minutes + ":" + (seconds < 10 ? "0" + seconds : seconds)
+ },
+
+ def _onPlayBtnClicked(self, btn):
+ if self.playing:
+ self.pause()
+ else:
+ self.play()
+
+ def _onNextBtnClicked(self, btn):
+ self.playNext()
+
+ def _onPrevBtnClicked(self, btn:
+ self.playPrevious()
+
+ def _setDuration(self, duration):
+ self.duration = duration
+ self.progressScale.set_range(0.0, duration*60)
+
+ def _updatePositionCallback(self):
+ position = self.player.query_position(Gst.Format.TIME, null)[1]/1000000000
+ if position >= 0:
+ self.progressScale.set_value(position * 60)
+ return True
+
+ def _syncRepeatImage(self):
+ icon = None
+ if self.repeat == RepeatType.NONE:
+ icon = 'media-playlist-consecutive-symbolic'
+ elif self.repeat == RepeatType.SHUFFLE:
+ icon = 'media-playlist-shuffle-symbolic'
+ elif self.repeat == RepeatType.ALL:
+ icon = 'media-playlist-repeat-symbolic'
+ elif self.repeat == RepeatType.SONG:
+ icon = 'media-playlist-repeat-song-symbolic'
+
+ self.repeatBtnImage.icon_name = icon
+ self._dbusImpl.emit_property_changed('LoopStatus', GLib.Variant.new('s', self.LoopStatus))
+ self._dbusImpl.emit_property_changed('Shuffle', GLib.Variant.new('b', self.Shuffle))
+
+
+ def onProgressScaleChangeValue(self, scroll):
+ seconds = scroll.get_value() / 60
+ if seconds != self.duration:
+ self.player.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, seconds *
1000000000)
+ self._dbusImpl.emit_signal('Seeked', GLib.Variant.new('(x)', [seconds * 1000000]))
+ else:
+ duration = self.player.query_duration(Gst.Format.TIME, None)
+ if duration:
+ #Rewind a second back before the track end
+ self.player.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT,
duration[1]-1000000000)
+ self._dbusImpl.emit_signal('Seeked', GLib.Variant.new('(x)',
[(duration[1]-1000000000)/1000]))
+ return True
+
+ #MPRIS
+
+ def Next(self):
+ self.playNext()
+
+ def Previous(self):
+ self.playPrevious()
+
+ def Pause(self):
+ self.setPlaying(False)
+
+ def PlayPause(self):
+ if self.player.get_state(1)[1] == Gst.State.PLAYING:
+ self.setPlaying(False)
+ else:
+ self.setPlaying(True)
+
+ def Play(self):
+ self.setPlaying(True)
+
+ def Stop(self):
+ self.progressScale.set_value(0)
+ self.progressScale.sensitive = false
+ self.playBtn.set_image(self._playImage)
+ self.stop()
+
+ def SeekAsync(self, params, invocation):
+ offset = params
+
+ duration = self.player.query_duration(Gst.Format.TIME, null)
+ if !duration:
+ return False
+
+ if offset < 0:
+ offset = 0
+
+ if duration[1] >= offset * 1000:
+ self.player.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, offset *
1000)
+ self._dbusImpl.emit_signal('Seeked', GLib.Variant.new('(x)', [offset]))
+ else:
+ self.playNext()
+
+ def SetPositionAsync(self, params, invocation):
+ trackId, position = params
+
+ if self.currentTrack == None:
+ return True
+
+ media = self.playlist.get_value(self.currentTrack, self.playlistField)
+ if trackId != '/org/mpris/MediaPlayer2/Track/' + media.get_id():
+ return True
+
+ duration = self.player.query_duration(Gst.Format.TIME, None)
+ if duration && position >= 0 && duration[1] >= position * 1000:
+ self.player.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, position
* 1000)
+ self._dbusImpl.emit_signal('Seeked', GLib.Variant.new('(x)', [position]))
+
+ def OpenUriAsync(self, params, invocation):
+ uri = params
+
+ def getPlaybackStatus(self):
+ ok, state, pending = self.player.get_state(0)
+ if ok == Gst.StateChangeReturn.ASYNC:
+ state = pending
+ elif (ok != Gst.StateChangeReturn.SUCCESS):
+ return 'Stopped'
+
+ if state == Gst.State.PLAYING:
+ return 'Playing'
+ elif state == Gst.State.PAUSED:
+ return 'Paused'
+ else:
+ return 'Stopped'
+
+ def getLoopStatus(self) {
+ if self.repeat == RepeatType.NONE:
+ return 'None'
+ elif self.repeat == RepeatType.SONG:
+ return 'Track'
+ else:
+ return 'Playlist'
+
+ def setLoopStatus(mode) {
+ if mode == 'None':
+ self.repeat = RepeatType.NONE
+ elif mode == 'Track':
+ self.repeat = RepeatType.SONG
+ elif mode == 'Playlist':
+ self.repeat = RepeatType.ALL
+ self._syncRepeatImage()
+
+ def getRate(self):
+ return 1.0
+
+ def setRate(self, rate):
+ pass
+
+ def getShuffle(self):
+ return self.repeat == RepeatType.SHUFFLE
+
+ def setShuffle(self, enable):
+ if (enable && self.repeat != RepeatType.SHUFFLE) {
+ self.repeat = RepeatType.SHUFFLE
+ elif !enable and self.repeat == RepeatType.SHUFFLE:
+ self.repeat = RepeatType.NONE
+ self._syncRepeatImage()
+
+ def getMetadata() {
+ if self.currentTrack == None:
+ return {}
+
+ media = self.playlist.get_value(self.currentTrack, self.playlistField)
+ metadata = {
+ 'mpris:trackid': GLib.Variant.new('s', '/org/mpris/MediaPlayer2/Track/' + media.get_id()),
+ 'xesam:url': GLib.Variant.new('s', media.get_url()),
+ 'mpris:length': GLib.Variant.new('x', media.get_duration()*1000000),
+ 'xesam:trackNumber': GLib.Variant.new('i', media.get_track_number()),
+ 'xesam:useCount': GLib.Variant.new('i', media.get_play_count()),
+ 'xesam:userRating': GLib.Variant.new('d', media.get_rating()),
+ }
+
+ title = media.get_title()
+ if title:
+ metadata['xesam:title'] = GLib.Variant.new('s', title)
+
+ album = media.get_album()
+ if album:
+ metadata['xesam:album'] = GLib.Variant.new('s', album)
+
+ artist = media.get_artist()
+ if artist:
+ metadata['xesam:artist'] = GLib.Variant.new('as', [artist])
+ metadata['xesam:albumArtist'] = GLib.Variant.new('as', [artist])
+
+ genre = media.get_genre()
+ if genre:
+ metadata['xesam:genre'] = GLib.Variant.new('as', [genre])
+
+ last_played = media.get_last_played()
+ if last_played:
+ metadata['xesam:lastUsed'] = GLib.Variant.new('s', last_played)
+
+ thumbnail = media.get_thumbnail()
+ if thumbnail:
+ metadata['mpris:artUrl'] = GLib.Variant.new('s', thumbnail)
+
+ return metadata
+
+ def getVolume(self):
+ return self.player.get_volume(GstAudio.StreamVolumeFormat.LINEAR)
+
+ def setVolume(self,rate):
+ self.player.set_volume(GstAudio.StreamVolumeFormat.LINEAR, rate)
+ self._dbusImpl.emit_property_changed('Volume', GLib.Variant.new('d', rate))
+
+ def getPosition(self)
+ return self.player.query_position(Gst.Format.TIME, None)[1]/1000
+
+ def getMinimumRate(self):
+ return 1.0
+
+ def getMaximumRate(self):
+ return 1.0
+
+ def getCanGoNext(self):
+ return self._hasNext()
+
+ def getCanGoPrevious(self):
+ return self._hasPrevious()
+
+ def getCanPlay(self):
+ return self.currentTrack != None
+
+ def getCanPause(self):
+ return self.currentTrack != None
+
+ def getCanSeek(self):
+ return True
+
+ def getCanControl(self):
+ return True
+
+class SelectionToolbar():
+ def __init__(self):
+ self._ui = Gtk.Builder()
+ self._ui.add_from_resource('/org/gnome/music/SelectionToolbar.ui')
+ self.eventbox = self._ui.get_object("eventbox1")
+ self._add_to_playlist_button = self._ui.get_object("button1")
+ self.eventbox.set_visible(false)
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]