123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- #!/usr/bin/env python
- # -*- coding: utf8 -*-
- #
- # Copyright © 2006-2011 Rafaël Carré <funman at videolanorg>
- #
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; either version 2 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software
- # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
- #
- #
- # NOTE: This controller is a SAMPLE, and thus doesn't use all the
- # Media Player Remote Interface Specification (MPRIS for short) capabilities
- #
- # MPRIS: http://www.mpris.org/2.1/spec/
- #
- # You'll need pygtk >= 2.12
- #
- # TODO
- # Ability to choose the Media Player if several are connected to the bus
- # core dbus stuff
- import dbus
- import dbus.glib
- # core interface stuff
- import gtk
- # timer
- from gobject import timeout_add
- # file loading
- import os
- global win_position # store the window position on the screen
- global playing
- playing = False
- global shuffle
- global root
- global player
- global tracklist
- global props
- global bus # Connection to the session bus
- mpris='org.mpris.MediaPlayer2'
- # If a Media Player connects to the bus, we'll use it
- # Note that we forget the previous Media Player we were connected to
- def NameOwnerChanged(name, new, old):
- if old != '' and mpris in name:
- Connect(name)
- def PropGet(prop):
- global props
- return props.Get(mpris + '.Player', prop)
- def PropSet(prop, val):
- global props
- props.Set(mpris + '.Player', prop, val)
- # Callback for when 'TrackChange' signal is emitted
- def TrackChange(Track):
- try:
- a = Track['xesam:artist']
- except:
- a = ''
- try:
- t = Track['xesam:title']
- except:
- t = Track['xesam:url']
- try:
- length = Track['mpris:length']
- except:
- length = 0
- if length > 0:
- time_s.set_range(0, length)
- time_s.set_sensitive(True)
- else:
- # disable the position scale if length isn't available
- time_s.set_sensitive(False)
- # update the labels
- l_artist.set_text(a)
- l_title.set_text(t)
- # Connects to the Media Player we detected
- def Connect(name):
- global root, player, tracklist, props
- global playing, shuffle
- root_o = bus.get_object(name, '/org/mpris/MediaPlayer2')
- root = dbus.Interface(root_o, mpris)
- tracklist = dbus.Interface(root_o, mpris + '.TrackList')
- player = dbus.Interface(root_o, mpris + '.Player')
- props = dbus.Interface(root_o, dbus.PROPERTIES_IFACE)
- # FIXME : doesn't exist anymore in mpris 2.1
- # connect to the TrackChange signal
- # root_o.connect_to_signal('TrackChange', TrackChange, dbus_interface=mpris)
- # determine if the Media Player is playing something
- if PropGet('PlaybackStatus') == 'Playing':
- playing = True
- TrackChange(PropGet('Metadata'))
- window.set_title(props.Get(mpris, 'Identity'))
- #plays an element
- def AddTrack(widget):
- mrl = e_mrl.get_text()
- if mrl != None and mrl != '':
- tracklist.AddTrack(mrl, '/', True)
- e_mrl.set_text('')
- else:
- mrl = bt_file.get_filename()
- if mrl != None and mrl != '':
- tracklist.AddTrack('directory://' + mrl, '/', True)
- update(0)
- # basic control
- def Next(widget):
- player.Next(reply_handler=(lambda *args: None), error_handler=(lambda *args: None))
- update(0)
- def Prev(widget):
- player.Prev(reply_handler=(lambda *args: None), error_handler=(lambda *args: None))
- update(0)
- def Stop(widget):
- player.Stop(reply_handler=(lambda *args: None), error_handler=(lambda *args: None))
- update(0)
- def Quit(widget):
- global props
- if props.Get(mpris, 'CanQuit'):
- root.Quit(reply_handler=(lambda *args: None), error_handler=(lambda *args: None))
- l_title.set_text('')
- window.set_title('')
- def Pause(widget):
- player.PlayPause()
- if PropGet('PlaybackStatus') == 'Playing':
- icon = gtk.STOCK_MEDIA_PAUSE
- else:
- icon = gtk.STOCK_MEDIA_PLAY
- img_bt_toggle.set_from_stock(icon, gtk.ICON_SIZE_SMALL_TOOLBAR)
- update(0)
- def Shuffle(widget):
- global shuffle
- shuffle = not shuffle
- PropSet('Shuffle', shuffle)
- # update status display
- def update(widget):
- Track = PropGet('Metadata')
- vol.set_value(PropGet('Volume') * 100.0)
- TrackChange(Track)
- GetPlayStatus(0)
- # callback for volume change
- def volchange(widget):
- PropSet('Volume', vol.get_value_as_int() / 100.0)
- # callback for position change
- def timechange(widget, x=None, y=None):
- player.SetPosition(PropGet('Metadata')['mpris:trackid'],
- time_s.get_value(),
- reply_handler=(lambda *args: None),
- error_handler=(lambda *args: None))
- # refresh position change
- def timeset():
- global playing
- if playing == True:
- try:
- time_s.set_value(PropGet('Position'))
- except:
- playing = False
- return True
- # toggle simple/full display
- def expander(widget):
- if exp.get_expanded() == False:
- exp.set_label('Less')
- else:
- exp.set_label('More')
- # close event : hide in the systray
- def delete_event(self, widget):
- self.hide()
- return True
- # shouldn't happen
- def destroy(widget):
- gtk.main_quit()
- # hide the controller when 'Esc' is pressed
- def key_release(widget, event):
- if event.keyval == gtk.keysyms.Escape:
- global win_position
- win_position = window.get_position()
- widget.hide()
- # callback for click on the tray icon
- def tray_button(widget):
- global win_position
- if window.get_property('visible'):
- # store position
- win_position = window.get_position()
- window.hide()
- else:
- # restore position
- window.move(win_position[0], win_position[1])
- window.show()
- # hack: update position, volume, and metadata
- def icon_clicked(widget, event):
- update(0)
- # get playing status, modify the Play/Pause button accordingly
- def GetPlayStatus(widget):
- global playing
- global shuffle
- playing = PropGet('PlaybackStatus') == 'Playing'
- if playing:
- img_bt_toggle.set_from_stock('gtk-media-pause', gtk.ICON_SIZE_SMALL_TOOLBAR)
- else:
- img_bt_toggle.set_from_stock('gtk-media-play', gtk.ICON_SIZE_SMALL_TOOLBAR)
- shuffle = PropGet('Shuffle')
- bt_shuffle.set_active( shuffle )
- # loads UI file from the directory where the script is,
- # so we can use /path/to/mpris.py to execute it.
- import sys
- xml = gtk.Builder()
- gtk.Builder.add_from_file(xml, os.path.join(os.path.dirname(sys.argv[0]) , 'mpris.xml'))
- # ui setup
- bt_close = xml.get_object('close')
- bt_quit = xml.get_object('quit')
- bt_file = xml.get_object('ChooseFile')
- bt_next = xml.get_object('next')
- bt_prev = xml.get_object('prev')
- bt_stop = xml.get_object('stop')
- bt_toggle = xml.get_object('toggle')
- bt_mrl = xml.get_object('AddMRL')
- bt_shuffle = xml.get_object('shuffle')
- l_artist = xml.get_object('l_artist')
- l_title = xml.get_object('l_title')
- e_mrl = xml.get_object('mrl')
- window = xml.get_object('window1')
- img_bt_toggle=xml.get_object('image6')
- exp = xml.get_object('expander2')
- expvbox = xml.get_object('expandvbox')
- audioicon = xml.get_object('eventicon')
- vol = xml.get_object('vol')
- time_s = xml.get_object('time_s')
- time_l = xml.get_object('time_l')
- # connect to the different callbacks
- window.connect('delete_event', delete_event)
- window.connect('destroy', destroy)
- window.connect('key_release_event', key_release)
- tray = gtk.status_icon_new_from_icon_name('audio-x-generic')
- tray.connect('activate', tray_button)
- bt_close.connect('clicked', destroy)
- bt_quit.connect('clicked', Quit)
- bt_mrl.connect('clicked', AddTrack)
- bt_toggle.connect('clicked', Pause)
- bt_next.connect('clicked', Next)
- bt_prev.connect('clicked', Prev)
- bt_stop.connect('clicked', Stop)
- bt_shuffle.connect('clicked', Shuffle)
- exp.connect('activate', expander)
- vol.connect('changed', volchange)
- time_s.connect('adjust-bounds', timechange)
- audioicon.set_events(gtk.gdk.BUTTON_PRESS_MASK) # hack for the bottom right icon
- audioicon.connect('button_press_event', icon_clicked)
- time_s.set_update_policy(gtk.UPDATE_DISCONTINUOUS)
- library = '/media/mp3' # editme
- # set the Directory chooser to a default location
- try:
- os.chdir(library)
- bt_file.set_current_folder(library)
- except:
- bt_file.set_current_folder(os.path.expanduser('~'))
- # connect to the bus
- bus = dbus.SessionBus()
- dbus_names = bus.get_object( 'org.freedesktop.DBus', '/org/freedesktop/DBus' )
- dbus_names.connect_to_signal('NameOwnerChanged', NameOwnerChanged, dbus_interface='org.freedesktop.DBus') # to detect new Media Players
- dbus_o = bus.get_object('org.freedesktop.DBus', '/')
- dbus_intf = dbus.Interface(dbus_o, 'org.freedesktop.DBus')
- # connect to the first Media Player found
- for n in dbus_intf.ListNames():
- if mpris in n:
- Connect(n)
- vol.set_value(PropGet('Volume') * 100.0)
- update(0)
- break
- # run a timer to update position
- timeout_add( 1000, timeset)
- window.set_icon_name('audio-x-generic')
- window.show()
- window.set_icon(gtk.icon_theme_get_default().load_icon('audio-x-generic',24,0))
- win_position = window.get_position()
- gtk.main() # execute the main loop
|