Optimize player and playlist instances

This commit is contained in:
tomkat83 2016-08-07 15:33:36 +02:00
parent ee802428f9
commit a2693b3485
8 changed files with 71 additions and 212 deletions

View file

@ -12,6 +12,7 @@ from plexbmchelper import listener, plexgdm, subscribers, functions, \
from PlexFunctions import ParseContainerKey, GetPlayQueue, \ from PlexFunctions import ParseContainerKey, GetPlayQueue, \
ConvertPlexToKodiTime ConvertPlexToKodiTime
import playlist import playlist
import player
@utils.logging @utils.logging
@ -21,7 +22,7 @@ class PlexCompanion(threading.Thread):
""" """
Initialize with a Queue for callbacks Initialize with a Queue for callbacks
""" """
def __init__(self, player=None): def __init__(self):
self.logMsg("----===## Starting PlexCompanion ##===----", 1) self.logMsg("----===## Starting PlexCompanion ##===----", 1)
self.settings = settings.getSettings() self.settings = settings.getSettings()
@ -36,7 +37,7 @@ class PlexCompanion(threading.Thread):
self.playlist = None self.playlist = None
# kodi player instance # kodi player instance
self.player = player self.player = player.Player()
threading.Thread.__init__(self) threading.Thread.__init__(self)
@ -127,7 +128,7 @@ class PlexCompanion(threading.Thread):
requestMgr = httppersist.RequestMgr() requestMgr = httppersist.RequestMgr()
jsonClass = functions.jsonClass(requestMgr, self.settings) jsonClass = functions.jsonClass(requestMgr, self.settings)
subscriptionManager = subscribers.SubscriptionManager( subscriptionManager = subscribers.SubscriptionManager(
jsonClass, requestMgr) jsonClass, requestMgr, self.player)
queue = Queue.Queue(maxsize=100) queue = Queue.Queue(maxsize=100)

View file

@ -20,10 +20,10 @@ from PlexFunctions import scrobble
@utils.logging @utils.logging
class KodiMonitor(xbmc.Monitor): class KodiMonitor(xbmc.Monitor):
def __init__(self, player=None): def __init__(self):
self.doUtils = downloadutils.DownloadUtils().downloadUrl self.doUtils = downloadutils.DownloadUtils().downloadUrl
self.xbmcplayer = player self.xbmcplayer = xbmc.Player()
xbmc.Monitor.__init__(self) xbmc.Monitor.__init__(self)
self.logMsg("Kodi monitor started.", 1) self.logMsg("Kodi monitor started.", 1)

View file

@ -1549,9 +1549,9 @@ class LibrarySync(Thread):
with itemFkt() as Fkt: with itemFkt() as Fkt:
Fkt.updatePlaystate(item) Fkt.updatePlaystate(item)
def run(self, player=None): def run(self):
try: try:
self.run_internal(player) self.run_internal()
except Exception as e: except Exception as e:
utils.window('plex_dbScan', clear=True) utils.window('plex_dbScan', clear=True)
self.logMsg('LibrarySync thread crashed', -1) self.logMsg('LibrarySync thread crashed', -1)
@ -1563,7 +1563,7 @@ class LibrarySync(Thread):
self.__language__(39400)) self.__language__(39400))
raise raise
def run_internal(self, player=None): def run_internal(self):
# Re-assign handles to have faster calls # Re-assign handles to have faster calls
window = utils.window window = utils.window
settings = utils.settings settings = utils.settings
@ -1582,7 +1582,7 @@ class LibrarySync(Thread):
lastProcessing = 0 lastProcessing = 0
oneDay = 60*60*24 oneDay = 60*60*24
xbmcplayer = player xbmcplayer = xbmc.Player()
queue = self.queue queue = self.queue

View file

@ -279,151 +279,6 @@ class Player(xbmc.Player):
else: else:
self.playStats[playMethod] = 1''' self.playStats[playMethod] = 1'''
def reportPlayback(self):
# Done by Plex Companion
return
self.logMsg("reportPlayback Called", 2)
# Get current file
currentFile = self.currentFile
data = self.played_info.get(currentFile)
# only report playback if emby has initiated the playback (item_id has value)
if data:
# Get playback inforation
itemId = data['item_id']
audioindex = data['AudioStreamIndex']
subtitleindex = data['SubtitleStreamIndex']
playTime = data['currentPosition']
playMethod = data['playmethod']
paused = data.get('paused', False)
duration = data.get('runtime', '')
# Get playback volume
volume_query = {
"jsonrpc": "2.0",
"id": 1,
"method": "Application.GetProperties",
"params": {
"properties": ["volume", "muted"]
}
}
result = xbmc.executeJSONRPC(json.dumps(volume_query))
result = json.loads(result)
result = result.get('result')
volume = result.get('volume')
muted = result.get('muted')
# Postdata for the websocketclient report
# postdata = {
# 'QueueableMediaTypes': "Video",
# 'CanSeek': True,
# 'ItemId': itemId,
# 'MediaSourceId': itemId,
# 'PlayMethod': playMethod,
# 'PositionTicks': int(playTime * 10000000),
# 'IsPaused': paused,
# 'VolumeLevel': volume,
# 'IsMuted': muted
# }
if paused == 'stopped':
state = 'stopped'
elif paused is True:
state = 'paused'
else:
state = 'playing'
postdata = {
'ratingKey': itemId,
'state': state, # 'stopped', 'paused', 'buffering', 'playing'
'time': int(playTime) * 1000,
'duration': int(duration) * 1000
}
# For PMS playQueues/playlists only
if data.get('playQueueID'):
postdata['containerKey'] = '/playQueues/' + data.get('playQueueID')
postdata['playQueueVersion'] = data.get('playQueueVersion')
postdata['playQueueItemID'] = data.get('playQueueItemID')
if playMethod == "Transcode":
# Track can't be changed, keep reporting the same index
postdata['AudioStreamIndex'] = audioindex
postdata['AudioStreamIndex'] = subtitleindex
else:
# Get current audio and subtitles track
tracks_query = {
"jsonrpc": "2.0",
"id": 1,
"method": "Player.GetProperties",
"params": {
"playerid": 1,
"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]
}
}
result = xbmc.executeJSONRPC(json.dumps(tracks_query))
result = json.loads(result)
result = result.get('result')
try: # Audio tracks
indexAudio = result['currentaudiostream']['index']
except (KeyError, TypeError):
indexAudio = 0
try: # Subtitles tracks
indexSubs = result['currentsubtitle']['index']
except (KeyError, TypeError):
indexSubs = 0
try: # If subtitles are enabled
subsEnabled = result['subtitleenabled']
except (KeyError, TypeError):
subsEnabled = ""
# Postdata for the audio
data['AudioStreamIndex'], postdata['AudioStreamIndex'] = [indexAudio + 1] * 2
# Postdata for the subtitles
if subsEnabled and len(xbmc.Player().getAvailableSubtitleStreams()) > 0:
# Number of audiotracks to help get Emby Index
audioTracks = len(xbmc.Player().getAvailableAudioStreams())
mapping = utils.window("emby_%s.indexMapping" % currentFile)
if mapping: # Set in PlaybackUtils.py
self.logMsg("Mapping for external subtitles index: %s" % mapping, 2)
externalIndex = json.loads(mapping)
if externalIndex.get(str(indexSubs)):
# If the current subtitle is in the mapping
subindex = [externalIndex[str(indexSubs)]] * 2
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
else:
# Internal subtitle currently selected
subindex = [indexSubs - len(externalIndex) + audioTracks + 1] * 2
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
else: # Direct paths enabled scenario or no external subtitles set
subindex = [indexSubs + audioTracks + 1] * 2
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
else:
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = [""] * 2
# Report progress via websocketclient
# postdata = json.dumps(postdata)
# self.ws.sendProgressUpdate(postdata)
self.doUtils(
"{server}/:/timeline?" + urlencode(postdata), action_type="GET")
def onPlayBackPaused(self): def onPlayBackPaused(self):
currentFile = self.currentFile currentFile = self.currentFile
@ -431,7 +286,6 @@ class Player(xbmc.Player):
if self.played_info.get(currentFile): if self.played_info.get(currentFile):
self.played_info[currentFile]['paused'] = True self.played_info[currentFile]['paused'] = True
self.reportPlayback()
def onPlayBackResumed(self): def onPlayBackResumed(self):
@ -440,7 +294,6 @@ class Player(xbmc.Player):
if self.played_info.get(currentFile): if self.played_info.get(currentFile):
self.played_info[currentFile]['paused'] = False self.played_info[currentFile]['paused'] = False
self.reportPlayback()
def onPlayBackSeek(self, time, seekOffset): def onPlayBackSeek(self, time, seekOffset):
# Make position when seeking a bit more accurate # Make position when seeking a bit more accurate
@ -454,7 +307,6 @@ class Player(xbmc.Player):
# When Kodi is not playing # When Kodi is not playing
return return
self.played_info[currentFile]['currentPosition'] = position * 1000 self.played_info[currentFile]['currentPosition'] = position * 1000
self.reportPlayback()
def onPlayBackStopped(self): def onPlayBackStopped(self):
# Will be called when user stops xbmc playing a file # Will be called when user stops xbmc playing a file

View file

@ -4,6 +4,8 @@
import json import json
from urllib import urlencode from urllib import urlencode
from threading import Lock
from functools import wraps
import xbmc import xbmc
@ -16,12 +18,32 @@ import PlexAPI
############################################################################### ###############################################################################
class lockMethod:
lock = Lock()
@classmethod
def decorate(cls, func):
@wraps(func)
def wrapper(*args, **kwargs):
with cls.lock:
result = func(*args, **kwargs)
return result
return wrapper
@utils.logging @utils.logging
class Playlist(): class Playlist():
""" """
Initiate with Playlist(typus='video' or 'music') Initiate with Playlist(typus='video' or 'music')
""" """
def __init__(self, typus=None, player=None): # Borg - multiple instances, shared state
_shared_state = {}
@lockMethod.decorate
def __init__(self, typus=None):
# Borg
self.__dict__ = self._shared_state
self.userid = utils.window('currUserId') self.userid = utils.window('currUserId')
self.server = utils.window('pms_server') self.server = utils.window('pms_server')
# Construct the Kodi playlist instance # Construct the Kodi playlist instance
@ -38,11 +60,11 @@ class Playlist():
self.typus = None self.typus = None
if self.playlist is not None: if self.playlist is not None:
self.playlistId = self.playlist.getPlayListId() self.playlistId = self.playlist.getPlayListId()
# kodi player instance self.player = xbmc.Player()
self.player = player
# "interal" PKC playlist # "interal" PKC playlist
self.items = [] self.items = []
@lockMethod.decorate
def clear(self): def clear(self):
""" """
Empties current Kodi playlist and internal self.items list Empties current Kodi playlist and internal self.items list
@ -90,7 +112,7 @@ class Playlist():
if self.playlist is not None: if self.playlist is not None:
self.playlistId = self.playlist.getPlayListId() self.playlistId = self.playlist.getPlayListId()
def _addToPlaylist(self, startitem, startPlayer=False): def _processItems(self, startitem, startPlayer=False):
startpos = None startpos = None
with embydb.GetEmbyDB() as emby_db: with embydb.GetEmbyDB() as emby_db:
for pos, item in enumerate(self.items): for pos, item in enumerate(self.items):
@ -113,7 +135,7 @@ class Playlist():
# Add to playlist # Add to playlist
self.logMsg("Adding %s PlexId %s, KodiId %s to playlist." self.logMsg("Adding %s PlexId %s, KodiId %s to playlist."
% (mediatype, plexId, kodiId), 1) % (mediatype, plexId, kodiId), 1)
self.addtoPlaylist(kodiId, mediatype) self._addtoPlaylist(kodiId, mediatype)
# Add the kodiId # Add the kodiId
if kodiId is not None: if kodiId is not None:
item['kodiId'] = str(kodiId) item['kodiId'] = str(kodiId)
@ -128,6 +150,7 @@ class Playlist():
'starting with the first entry', 1) 'starting with the first entry', 1)
self.player.play(self.playlist) self.player.play(self.playlist)
@lockMethod.decorate
def playAll(self, items, startitem, offset): def playAll(self, items, startitem, offset):
""" """
items: list of dicts of the form items: list of dicts of the form
@ -155,21 +178,26 @@ class Playlist():
if offset != 0: if offset != 0:
# Seek to the starting position # Seek to the starting position
utils.window('plex_customplaylist.seektime', str(offset)) utils.window('plex_customplaylist.seektime', str(offset))
self._addToPlaylist(startitem, startPlayer=True) self._processItems(startitem, startPlayer=True)
# Log playlist # Log playlist
self.verifyPlaylist() self._verifyPlaylist()
self.logMsg('Internal playlist: %s' % self.items, 2) self.logMsg('Internal playlist: %s' % self.items, 2)
@lockMethod.decorate
def modifyPlaylist(self, itemids): def modifyPlaylist(self, itemids):
self.logMsg("---*** ADD TO PLAYLIST ***---", 1) self.logMsg("---*** ADD TO PLAYLIST ***---", 1)
self.logMsg("Items: %s" % itemids, 1) self.logMsg("Items: %s" % itemids, 1)
self._initiatePlaylist(itemids) self._initiatePlaylist(itemids)
self._addToPlaylist(itemids, startPlayer=True) self._processItems(itemids, startPlayer=True)
self.verifyPlaylist() self._verifyPlaylist()
@lockMethod.decorate
def addtoPlaylist(self, dbid=None, mediatype=None, url=None): def addtoPlaylist(self, dbid=None, mediatype=None, url=None):
self._addtoPlaylist(dbid=None, mediatype=None, url=None)
def _addtoPlaylist(self, dbid=None, mediatype=None, url=None):
""" """
mediatype: Kodi type: 'movie', 'episode', 'musicvideo', 'artist', mediatype: Kodi type: 'movie', 'episode', 'musicvideo', 'artist',
'album', 'song', 'genre' 'album', 'song', 'genre'
@ -205,6 +233,7 @@ class Playlist():
self.playlist.add(playurl, listitem) self.playlist.add(playurl, listitem)
@lockMethod.decorate
def insertintoPlaylist(self, position, dbid=None, mediatype=None, url=None): def insertintoPlaylist(self, position, dbid=None, mediatype=None, url=None):
pl = { pl = {
@ -226,8 +255,11 @@ class Playlist():
self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2) self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
@lockMethod.decorate
def verifyPlaylist(self): def verifyPlaylist(self):
self._verifyPlaylist()
def _verifyPlaylist(self):
pl = { pl = {
'jsonrpc': "2.0", 'jsonrpc': "2.0",
@ -241,6 +273,7 @@ class Playlist():
} }
self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2) self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
@lockMethod.decorate
def removefromPlaylist(self, position): def removefromPlaylist(self, position):
pl = { pl = {

View file

@ -1,8 +1,6 @@
import re import re
import threading import threading
from xbmc import Player
import downloadutils import downloadutils
from utils import window, logging from utils import window, logging
import PlexFunctions as pf import PlexFunctions as pf
@ -11,7 +9,7 @@ from functions import *
@logging @logging
class SubscriptionManager: class SubscriptionManager:
def __init__(self, jsonClass, RequestMgr): def __init__(self, jsonClass, RequestMgr, player):
self.serverlist = [] self.serverlist = []
self.subscribers = {} self.subscribers = {}
self.info = {} self.info = {}
@ -25,7 +23,7 @@ class SubscriptionManager:
self.port = "" self.port = ""
self.playerprops = {} self.playerprops = {}
self.doUtils = downloadutils.DownloadUtils().downloadUrl self.doUtils = downloadutils.DownloadUtils().downloadUrl
self.xbmcplayer = Player() self.xbmcplayer = player
self.js = jsonClass self.js = jsonClass
self.RequestMgr = RequestMgr self.RequestMgr = RequestMgr

View file

@ -16,6 +16,7 @@ from functools import wraps
from calendar import timegm from calendar import timegm
import os import os
import xbmc import xbmc
import xbmcaddon import xbmcaddon
import xbmcgui import xbmcgui
@ -111,6 +112,19 @@ def IfExists(path):
return answer return answer
def forEveryMethod(decorator):
"""
Wrapper for classes to add the decorator "decorator" to all methods of the
class
"""
def decorate(cls):
for attr in cls.__dict__: # there's propably a better way to do this
if callable(getattr(cls, attr)):
setattr(cls, attr, decorator(getattr(cls, attr)))
return cls
return decorate
def CatchExceptions(warnuser=False): def CatchExceptions(warnuser=False):
""" """
Decorator for methods to catch exceptions and log them. Useful for e.g. Decorator for methods to catch exceptions and log them. Useful for e.g.

View file

@ -4,7 +4,6 @@
import os import os
import sys import sys
from datetime import datetime
import Queue import Queue
import xbmc import xbmc
@ -39,7 +38,6 @@ import clientinfo
import initialsetup import initialsetup
import kodimonitor import kodimonitor
import librarysync import librarysync
import player
import videonodes import videonodes
import websocket_client as wsc import websocket_client as wsc
import downloadutils import downloadutils
@ -139,12 +137,8 @@ class Service():
user = userclient.UserClient() user = userclient.UserClient()
ws = wsc.WebSocket(queue) ws = wsc.WebSocket(queue)
library = librarysync.LibrarySync(queue) library = librarysync.LibrarySync(queue)
kplayer = player.Player()
plx = PlexAPI.PlexAPI() plx = PlexAPI.PlexAPI()
# Sync and progress report
lastProgressUpdate = datetime.today()
counter = 0 counter = 0
while not monitor.abortRequested(): while not monitor.abortRequested():
@ -153,7 +147,6 @@ class Service():
log("Kodi profile was: %s and changed to: %s. Terminating old " log("Kodi profile was: %s and changed to: %s. Terminating old "
"PlexKodiConnect thread." "PlexKodiConnect thread."
% (kodiProfile, utils.window('plex_kodiProfile')), 1) % (kodiProfile, utils.window('plex_kodiProfile')), 1)
break break
# Before proceeding, need to make sure: # Before proceeding, need to make sure:
@ -165,36 +158,6 @@ class Service():
# Plex server is online # Plex server is online
# Verify if user is set and has access to the server # Verify if user is set and has access to the server
if (user.currUser is not None) and user.HasAccess: if (user.currUser is not None) and user.HasAccess:
# If an item is playing
if kplayer.isPlaying():
try:
# Update and report progress
playtime = kplayer.getTime()
totalTime = kplayer.getTotalTime()
currentFile = kplayer.currentFile
# Update positionticks
if kplayer.played_info.get(currentFile) is not None:
kplayer.played_info[currentFile]['currentPosition'] = playtime
td = datetime.today() - lastProgressUpdate
secDiff = td.seconds
# Report progress to Plex server
if (secDiff > 3):
kplayer.reportPlayback()
lastProgressUpdate = datetime.today()
elif window('plex_command') == "true":
# Received a remote control command that
# requires updating immediately
window('plex_command', clear=True)
kplayer.reportPlayback()
lastProgressUpdate = datetime.today()
except Exception as e:
log("Exception in Playback Monitor Service: %s" % e, 1)
pass
if not self.kodimonitor_running: if not self.kodimonitor_running:
# Start up events # Start up events
self.warn_auth = True self.warn_auth = True
@ -208,8 +171,7 @@ class Service():
time=2000, time=2000,
sound=False) sound=False)
# Start monitoring kodi events # Start monitoring kodi events
self.kodimonitor_running = kodimonitor.KodiMonitor( self.kodimonitor_running = kodimonitor.KodiMonitor()
player=kplayer)
# Start the Websocket Client # Start the Websocket Client
if not self.websocket_running: if not self.websocket_running:
@ -222,8 +184,7 @@ class Service():
# Start the Plex Companion thread # Start the Plex Companion thread
if not self.plexCompanion_running: if not self.plexCompanion_running:
self.plexCompanion_running = True self.plexCompanion_running = True
plexCompanion = PlexCompanion.PlexCompanion( plexCompanion = PlexCompanion.PlexCompanion()
player=kplayer)
plexCompanion.start() plexCompanion.start()
else: else:
if (user.currUser is None) and self.warn_auth: if (user.currUser is None) and self.warn_auth: