464 lines
16 KiB
Python
Executable File
464 lines
16 KiB
Python
Executable File
# License for this source file
|
|
#
|
|
# Copyright (c) 2021 Miqra Engineering
|
|
#
|
|
|
|
import sys, os
|
|
import signal
|
|
|
|
from optparse import OptionParser
|
|
from collections import OrderedDict
|
|
|
|
from .resources import Resources
|
|
|
|
# perform gstreamer imports (python3 style)
|
|
import gi
|
|
gi.require_version('Gst', '1.0')
|
|
gi.require_version('Gtk', '3.0')
|
|
gi.require_version('GstGL', '1.0')
|
|
gi.require_version('GstVideo', '1.0')
|
|
gi.require_version('GdkPixbuf', '2.0')
|
|
|
|
from gi.repository import GObject
|
|
from gi.repository import Gst, GstGL, GstVideo
|
|
from gi.repository import GdkPixbuf
|
|
from gi.repository import GLib, Gio, Gtk, Gdk
|
|
from gi.repository import GdkX11
|
|
|
|
Gst.init(None)
|
|
|
|
# perform dbus imports
|
|
import dbus
|
|
import dbus.service
|
|
from dbus.mainloop.glib import DBusGMainLoop
|
|
|
|
# set dbus to use GObject Mainloop
|
|
DBusGMainLoop(set_as_default=True)
|
|
|
|
# imports from module
|
|
from . monitoredplayer import MonitoredPlayer
|
|
|
|
|
|
class MediaService(dbus.service.Object):
|
|
def __init__(self):
|
|
|
|
|
|
bus_name = dbus.service.BusName('nl.miqra.MediaCore.Media', bus=dbus.SystemBus())
|
|
dbus.service.Object.__init__(self, bus_name, '/nl/miqra/MediaCore/Media')
|
|
|
|
self.window = Gtk.Window(title="MediaServer")
|
|
self.windowInit()
|
|
|
|
self.player = MonitoredPlayer()
|
|
|
|
self.player.connect('playback-ready',self.onPlayerReady) # parameters: source_file tag_dict
|
|
self.player.connect('playback-playing',self.onPlayerPlaying) # parameters: None
|
|
self.player.connect('playback-stopped',self.onPlayerStopped) # parameters: None
|
|
self.player.connect('playback-paused',self.onPlayerPaused) # parameters: None
|
|
self.player.connect('playback-finished',self.onPlayerFinished) # parameters: None
|
|
self.player.connect('playback-error',self.onPlayerError) # parameters: error debug
|
|
self.player.connect('volume-changed',self.onPlayerVolumeChanged) # parameters: volume
|
|
self.player.connect('playback-buffering',self.onPlayerBuffering) # parameters: volume
|
|
|
|
self.window.connect('show',self.onWindowShow)
|
|
|
|
self.loadcount = 0
|
|
self.loadmax = 1
|
|
|
|
self.quickplay = False
|
|
self.quickplayduration = 0
|
|
self.quickplayloop = False
|
|
|
|
def Start(self):
|
|
self.window.show()
|
|
self.tick()
|
|
|
|
# Keepalive timer to help react to Keyboard interrupts
|
|
def tick(self):
|
|
GObject.timeout_add(100, self.tick)
|
|
|
|
def windowInit(self):
|
|
self.window.set_name("mainwindow")
|
|
self.imagebox = Gtk.Box()
|
|
self.imagebox.set_name("imbox")
|
|
self.imagebox.set_hexpand(True)
|
|
self.imagebox.set_vexpand(True)
|
|
self.imagebox.set_halign(Gtk.Align.CENTER)
|
|
self.imagebox.set_valign(Gtk.Align.CENTER)
|
|
|
|
css = b"""
|
|
#mainwindow
|
|
{
|
|
background-color: #000;
|
|
}
|
|
"""
|
|
css_provider = Gtk.CssProvider()
|
|
css_provider.load_from_data(css)
|
|
context = Gtk.StyleContext()
|
|
screen = Gdk.Screen.get_default()
|
|
context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
|
|
|
|
self.window.add(self.imagebox)
|
|
|
|
self.window.fullscreen()
|
|
pass
|
|
|
|
def loadImage(self,image):
|
|
# remove any existing children
|
|
for child in self.imagebox.get_children():
|
|
self.imagebox.remove(child)
|
|
|
|
self.imagebox.add(image)
|
|
self.imagebox.show_all()
|
|
|
|
def loadImageFile(self,imfile):
|
|
print("Loading image {0}".format(imfile))
|
|
|
|
image = Gtk.Image()
|
|
image.set_from_file(imfile)
|
|
self.loadImage(image)
|
|
|
|
def loadImageData(self,data):
|
|
print("Loading image from data")
|
|
loader = GdkPixbuf.PixbufLoader.new()
|
|
loader.write(data)
|
|
pixbuf = loader.get_pixbuf()
|
|
loader.close()
|
|
image = Gtk.Image()
|
|
image.set_from_pixbuf(pixbuf)
|
|
self.loadImage(image)
|
|
|
|
def onWindowShow(self,w):
|
|
xid = self.window.get_window().get_xid()
|
|
print("Retrieved XID for window: {0}".format(xid))
|
|
self.player.link_to_window(xid)
|
|
self.loadBaseImage()
|
|
|
|
def loadBaseImage(self):
|
|
self.loadImageFile(Resources.filename("images/background-base.svg"))
|
|
|
|
|
|
@dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='s', out_signature='')
|
|
def QuickPlay(self, file,):
|
|
""" Directly play back a local file
|
|
"""
|
|
if self.loadcount < self.loadmax:
|
|
print("Quickplaying file {0}".format(file))
|
|
self.quickplay = True
|
|
self.quickplayduration = 0
|
|
self.quickplayloop = False
|
|
self.player.load(file)
|
|
self.loadcount += 1
|
|
else:
|
|
print("Skipping load of file {0} because maximum simultaneous loads ({1}) was reached. Wait until ready....".format(file,self.loadmax))
|
|
|
|
@dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='sd', out_signature='')
|
|
def QuickPlayFor(self, file, duration):
|
|
""" Directly play back a local file
|
|
"""
|
|
if self.loadcount < self.loadmax:
|
|
print("Quickplaying file {0} for {1} seconds".format(file,duration))
|
|
self.quickplay = True
|
|
self.quickplayduration = duration
|
|
self.quickplayloop = False
|
|
self.player.load(file)
|
|
self.loadcount += 1
|
|
else:
|
|
print("Skipping load of file {0} because maximum simultaneous loads ({1}) was reached. Wait until ready....".format(file,self.loadmax))
|
|
# self.quickplayer.playfor(file,duration)
|
|
|
|
@dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='s', out_signature='')
|
|
def QuickPlayUrl(self, url):
|
|
""" Directly play back a url
|
|
"""
|
|
if self.loadcount < self.loadmax:
|
|
print("Quickplaying url {0}".format(url))
|
|
self.quickplay = True
|
|
self.quickplayduration = 0
|
|
self.quickplayloop = False
|
|
self.player.load_uri(url)
|
|
self.loadcount += 1
|
|
else:
|
|
print("Skipping load of file {0} because maximum simultaneous loads ({1}) was reached. Wait until ready....".format(url,self.loadmax))
|
|
|
|
@dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='sd', out_signature='')
|
|
def QuickPlayUrlFor(self, url, duration):
|
|
""" Directly play back a url
|
|
"""
|
|
if self.loadcount < self.loadmax:
|
|
print("Quickplaying url {0} for {1} seconds".format(url,duration))
|
|
self.quickplay = True
|
|
self.quickplayduration = duration
|
|
self.quickplayloop = False
|
|
self.player.load_uri(url)
|
|
self.loadcount += 1
|
|
else:
|
|
print("Skipping load of file {0} because maximum simultaneous loads ({1}) was reached. Wait until ready....".format(url,self.loadmax))
|
|
|
|
@dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='s', out_signature='')
|
|
def QuickLoop(self, file):
|
|
""" Directly play back a url
|
|
"""
|
|
if self.loadcount < self.loadmax:
|
|
print("Quickplaying file {0}".format(file))
|
|
self.quickplay = True
|
|
self.quickplayduration = 0
|
|
self.quickplayloop = True
|
|
self.player.load(file)
|
|
self.loadcount += 1
|
|
else:
|
|
print("Skipping load of file {0} because maximum simultaneous loads ({1}) was reached. Wait until ready....".format(file,self.loadmax))
|
|
|
|
@dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='s', out_signature='')
|
|
def QuickLoopUrl(self, url):
|
|
""" Directly play back a url
|
|
"""
|
|
if self.loadcount < self.loadmax:
|
|
print("Quickplaying url {0}".format(url))
|
|
self.quickplay = True
|
|
self.quickplayduration = 0
|
|
self.quickplayloop = True
|
|
self.player.load_uri(url)
|
|
self.loadcount += 1
|
|
else:
|
|
print("Skipping load of file {0} because maximum simultaneous loads ({1}) was reached. Wait until ready....".format(url,self.loadmax))
|
|
|
|
|
|
|
|
|
|
@dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='s', out_signature='')
|
|
def LoadFile(self, file):
|
|
""" Load a local file for playback
|
|
"""
|
|
if self.loadcount < self.loadmax:
|
|
print("Loading file {0}".format(file))
|
|
self.quickplay = False
|
|
self.player.load(file)
|
|
self.loadcount += 1
|
|
self.OnLoading("file://{0}".format(file))
|
|
else:
|
|
print("Skipping load of file {0} because maximum simultaneous loads ({1}) was reached. Wait until ready....".format(file,self.loadmax))
|
|
|
|
@dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='s', out_signature='')
|
|
def LoadUrl(self, uri):
|
|
""" Load an url for playback
|
|
"""
|
|
if self.loadcount < self.loadmax:
|
|
print("Loading url {0}".format(uri))
|
|
self.quickplay = False
|
|
self.player.load_uri(uri)
|
|
self.loadcount += 1
|
|
self.OnLoading(uri)
|
|
else:
|
|
print("Skipping load of url {0} because maximum simultaneous loads ({1}) was reached. Wait until ready....".format(uri,self.loadmax))
|
|
|
|
@dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='d', out_signature='')
|
|
def PlayFor(self, duration):
|
|
""" Starts playback for [duration] seconds
|
|
"""
|
|
print("Starting playback for {0} seconds".format(duration))
|
|
self.player.playfor(duration)
|
|
|
|
@dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='', out_signature='')
|
|
def Play(self):
|
|
""" Starts/resumes playback
|
|
"""
|
|
pos = self.player.position()
|
|
print("Starting playback at timestamp {0}".format(pos))
|
|
self.player.play()
|
|
|
|
@dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='', out_signature='')
|
|
def Loop(self):
|
|
""" Starts/resumes playback
|
|
"""
|
|
pos = self.player.position()
|
|
print("Starting playback at timestamp {0}".format(pos))
|
|
self.player.loop()
|
|
|
|
@dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='', out_signature='')
|
|
def Pause(self):
|
|
""" Pauses playback
|
|
"""
|
|
print("Pausing")
|
|
self.player.pause()
|
|
|
|
@dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='', out_signature='')
|
|
def Stop(self):
|
|
""" Stops playback
|
|
"""
|
|
print("Stopping")
|
|
self.player.stop()
|
|
|
|
@dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='', out_signature='d')
|
|
def Length(self):
|
|
""" returns length of currently loaded source
|
|
"""
|
|
return self.player.duration()
|
|
|
|
@dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='', out_signature='d')
|
|
def Position(self):
|
|
""" returns current position in the source
|
|
"""
|
|
return self.player.position()
|
|
|
|
@dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='d', out_signature='b')
|
|
def Seek(self,position):
|
|
""" returns current position in the source
|
|
"""
|
|
self.player.seek(position)
|
|
|
|
### Callback functions
|
|
def onPlayerReady(self,player,filename,tags):
|
|
print("Loaded {0}".format(filename))
|
|
print("Tags:")
|
|
taglist = {}
|
|
for tag in tags:
|
|
try:
|
|
taglist[tag] = str(tags[tag])
|
|
print(" {0}: {1}".format(tag,taglist[tag]))
|
|
except Exception as x:
|
|
print("Error converting value for '{0}':\n {1}".format(tag,x))
|
|
|
|
# extract cover art from tags
|
|
if "image" in tags:
|
|
buffer = tags["image"].get_buffer() # Gst.Buffer
|
|
(result, mapinfo) = buffer.map(Gst.MapFlags.READ)
|
|
if result:
|
|
self.loadImageData(mapinfo.data)
|
|
|
|
|
|
# reset load counter
|
|
self.loadcount = 0
|
|
|
|
if self.quickplay:
|
|
if self.quickplayloop:
|
|
print("Direct looping")
|
|
self.player.loop()
|
|
elif self.quickplayduration > 0:
|
|
print("Direct playing for {0} s".format(self.quickplayduration))
|
|
self.player.playfor(self.quickplayduration)
|
|
else:
|
|
print("Direct playing")
|
|
self.player.play()
|
|
else:
|
|
# signal ready
|
|
self.OnReady(filename,taglist)
|
|
|
|
def onPlayerPlaying(self,player):
|
|
self.OnPlaying()
|
|
|
|
def onPlayerStopped(self,player):
|
|
self.loadBaseImage()
|
|
self.OnStopped()
|
|
|
|
def onPlayerPaused(self,player):
|
|
self.OnPaused()
|
|
|
|
def onPlayerFinished(self,player):
|
|
self.loadBaseImage()
|
|
self.OnFinished()
|
|
|
|
def onPlayerError(self,player,state,error,debug):
|
|
print("Player state: {0}".format(state))
|
|
if state == "LOADING":
|
|
print("Failure during LOAD: {0}".format(error))
|
|
self.OnLoadFail(error)
|
|
else:
|
|
print("Failure during RUN: {0}".format(error))
|
|
self.OnRunFail(error)
|
|
|
|
def onPlayerVolumeChanged(self,player,volume):
|
|
pass
|
|
|
|
def onPlayerBuffering(self,player,pct):
|
|
self.OnBuffering(pct)
|
|
pass
|
|
|
|
## Signalling functions (DBus)
|
|
|
|
@dbus.service.signal(dbus_interface='nl.miqra.MediaCore.Media', signature='s')
|
|
def OnLoading(self,url):
|
|
""" gets called when a source has been requested for playback
|
|
"""
|
|
pass
|
|
|
|
@dbus.service.signal(dbus_interface='nl.miqra.MediaCore.Media', signature='sa{ss}')
|
|
def OnReady(self,filename,tags):
|
|
""" gets called when a source is ready for playback
|
|
"""
|
|
pass
|
|
|
|
@dbus.service.signal(dbus_interface='nl.miqra.MediaCore.Media', signature='')
|
|
def OnPlaying(self):
|
|
""" gets called when a playback has started
|
|
"""
|
|
pass
|
|
|
|
@dbus.service.signal(dbus_interface='nl.miqra.MediaCore.Media', signature='')
|
|
def OnPaused(self):
|
|
""" gets called when a playback is paused
|
|
"""
|
|
pass
|
|
|
|
@dbus.service.signal(dbus_interface='nl.miqra.MediaCore.Media', signature='q')
|
|
def OnBuffering(self,percent):
|
|
""" gets called when a playback is paused
|
|
"""
|
|
pass
|
|
|
|
@dbus.service.signal(dbus_interface='nl.miqra.MediaCore.Media', signature='')
|
|
def OnStopped(self):
|
|
""" gets called when playback has stopped
|
|
"""
|
|
pass
|
|
|
|
@dbus.service.signal(dbus_interface='nl.miqra.MediaCore.Media', signature='')
|
|
def OnFinished(self):
|
|
""" gets called when playback has completed
|
|
"""
|
|
pass
|
|
|
|
@dbus.service.signal(dbus_interface='nl.miqra.MediaCore.Media', signature='')
|
|
def OnFinishing(self):
|
|
""" gets called when playback is nearly completed
|
|
"""
|
|
pass
|
|
|
|
@dbus.service.signal(dbus_interface='nl.miqra.MediaCore.Media', signature='s')
|
|
def OnLoadFail(self,reason):
|
|
""" gets called when loading a source for playback fails
|
|
"""
|
|
pass
|
|
|
|
@dbus.service.signal(dbus_interface='nl.miqra.MediaCore.Media', signature='s')
|
|
def OnRunFail(self,reason):
|
|
""" gets called when playback fails
|
|
"""
|
|
pass
|
|
|
|
def Run():
|
|
|
|
print("Initializing")
|
|
|
|
# quick bugfix - set XDG runtime dir if it has not been set
|
|
if not 'XDG_RUNTIME_DIR' in os.environ:
|
|
os.environ['XDG_RUNTIME_DIR'] = "/run/user/{0}".format(os.getuid())
|
|
|
|
mediaservice = MediaService()
|
|
mediaservice.Start()
|
|
loop = GObject.MainLoop()
|
|
|
|
|
|
def onsigint(signal,frame):
|
|
print("Quitting")
|
|
loop.quit()
|
|
|
|
signal.signal(signal.SIGINT, onsigint)
|
|
|
|
print("Starting..." )
|
|
try:
|
|
loop.run()
|
|
except KeyboardInterrupt:
|
|
print("Quitting")
|
|
loop.quit()
|