diff --git a/resources/lib/PlexCompanion.py b/resources/lib/PlexCompanion.py index 29f2fe82..ca6e161c 100644 --- a/resources/lib/PlexCompanion.py +++ b/resources/lib/PlexCompanion.py @@ -12,6 +12,7 @@ from plexbmchelper import listener, plexgdm, subscribers, functions, \ from PlexFunctions import ParseContainerKey, GetPlayQueue, \ ConvertPlexToKodiTime import playlist +import player @utils.logging @@ -21,7 +22,7 @@ class PlexCompanion(threading.Thread): """ Initialize with a Queue for callbacks """ - def __init__(self, player=None): + def __init__(self): self.logMsg("----===## Starting PlexCompanion ##===----", 1) self.settings = settings.getSettings() @@ -36,7 +37,7 @@ class PlexCompanion(threading.Thread): self.playlist = None # kodi player instance - self.player = player + self.player = player.Player() threading.Thread.__init__(self) @@ -127,7 +128,7 @@ class PlexCompanion(threading.Thread): requestMgr = httppersist.RequestMgr() jsonClass = functions.jsonClass(requestMgr, self.settings) subscriptionManager = subscribers.SubscriptionManager( - jsonClass, requestMgr) + jsonClass, requestMgr, self.player) queue = Queue.Queue(maxsize=100) diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py index 5a814402..97b807b1 100644 --- a/resources/lib/kodimonitor.py +++ b/resources/lib/kodimonitor.py @@ -20,10 +20,10 @@ from PlexFunctions import scrobble @utils.logging class KodiMonitor(xbmc.Monitor): - def __init__(self, player=None): + def __init__(self): self.doUtils = downloadutils.DownloadUtils().downloadUrl - self.xbmcplayer = player + self.xbmcplayer = xbmc.Player() xbmc.Monitor.__init__(self) self.logMsg("Kodi monitor started.", 1) diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index 7c87d6f4..9eeaed22 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -1549,9 +1549,9 @@ class LibrarySync(Thread): with itemFkt() as Fkt: Fkt.updatePlaystate(item) - def run(self, player=None): + def run(self): try: - self.run_internal(player) + self.run_internal() except Exception as e: utils.window('plex_dbScan', clear=True) self.logMsg('LibrarySync thread crashed', -1) @@ -1563,7 +1563,7 @@ class LibrarySync(Thread): self.__language__(39400)) raise - def run_internal(self, player=None): + def run_internal(self): # Re-assign handles to have faster calls window = utils.window settings = utils.settings @@ -1582,7 +1582,7 @@ class LibrarySync(Thread): lastProcessing = 0 oneDay = 60*60*24 - xbmcplayer = player + xbmcplayer = xbmc.Player() queue = self.queue diff --git a/resources/lib/player.py b/resources/lib/player.py index 93e02e98..a77315c0 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -279,151 +279,6 @@ class Player(xbmc.Player): else: 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): currentFile = self.currentFile @@ -431,7 +286,6 @@ class Player(xbmc.Player): if self.played_info.get(currentFile): self.played_info[currentFile]['paused'] = True - self.reportPlayback() def onPlayBackResumed(self): @@ -440,7 +294,6 @@ class Player(xbmc.Player): if self.played_info.get(currentFile): self.played_info[currentFile]['paused'] = False - self.reportPlayback() def onPlayBackSeek(self, time, seekOffset): # Make position when seeking a bit more accurate @@ -454,7 +307,6 @@ class Player(xbmc.Player): # When Kodi is not playing return self.played_info[currentFile]['currentPosition'] = position * 1000 - self.reportPlayback() def onPlayBackStopped(self): # Will be called when user stops xbmc playing a file diff --git a/resources/lib/playlist.py b/resources/lib/playlist.py index a38b1897..a5ef9396 100644 --- a/resources/lib/playlist.py +++ b/resources/lib/playlist.py @@ -4,6 +4,8 @@ import json from urllib import urlencode +from threading import Lock +from functools import wraps 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 class Playlist(): """ 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.server = utils.window('pms_server') # Construct the Kodi playlist instance @@ -38,11 +60,11 @@ class Playlist(): self.typus = None if self.playlist is not None: self.playlistId = self.playlist.getPlayListId() - # kodi player instance - self.player = player + self.player = xbmc.Player() # "interal" PKC playlist self.items = [] + @lockMethod.decorate def clear(self): """ Empties current Kodi playlist and internal self.items list @@ -90,7 +112,7 @@ class Playlist(): if self.playlist is not None: self.playlistId = self.playlist.getPlayListId() - def _addToPlaylist(self, startitem, startPlayer=False): + def _processItems(self, startitem, startPlayer=False): startpos = None with embydb.GetEmbyDB() as emby_db: for pos, item in enumerate(self.items): @@ -113,7 +135,7 @@ class Playlist(): # Add to playlist self.logMsg("Adding %s PlexId %s, KodiId %s to playlist." % (mediatype, plexId, kodiId), 1) - self.addtoPlaylist(kodiId, mediatype) + self._addtoPlaylist(kodiId, mediatype) # Add the kodiId if kodiId is not None: item['kodiId'] = str(kodiId) @@ -128,6 +150,7 @@ class Playlist(): 'starting with the first entry', 1) self.player.play(self.playlist) + @lockMethod.decorate def playAll(self, items, startitem, offset): """ items: list of dicts of the form @@ -155,21 +178,26 @@ class Playlist(): if offset != 0: # Seek to the starting position utils.window('plex_customplaylist.seektime', str(offset)) - self._addToPlaylist(startitem, startPlayer=True) + self._processItems(startitem, startPlayer=True) # Log playlist - self.verifyPlaylist() + self._verifyPlaylist() self.logMsg('Internal playlist: %s' % self.items, 2) + @lockMethod.decorate def modifyPlaylist(self, itemids): self.logMsg("---*** ADD TO PLAYLIST ***---", 1) self.logMsg("Items: %s" % itemids, 1) 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): + self._addtoPlaylist(dbid=None, mediatype=None, url=None) + + def _addtoPlaylist(self, dbid=None, mediatype=None, url=None): """ mediatype: Kodi type: 'movie', 'episode', 'musicvideo', 'artist', 'album', 'song', 'genre' @@ -205,6 +233,7 @@ class Playlist(): self.playlist.add(playurl, listitem) + @lockMethod.decorate def insertintoPlaylist(self, position, dbid=None, mediatype=None, url=None): pl = { @@ -226,8 +255,11 @@ class Playlist(): self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2) + @lockMethod.decorate def verifyPlaylist(self): + self._verifyPlaylist() + def _verifyPlaylist(self): pl = { 'jsonrpc': "2.0", @@ -241,6 +273,7 @@ class Playlist(): } self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2) + @lockMethod.decorate def removefromPlaylist(self, position): pl = { diff --git a/resources/lib/plexbmchelper/subscribers.py b/resources/lib/plexbmchelper/subscribers.py index 64d70224..4002de5b 100644 --- a/resources/lib/plexbmchelper/subscribers.py +++ b/resources/lib/plexbmchelper/subscribers.py @@ -1,8 +1,6 @@ import re import threading -from xbmc import Player - import downloadutils from utils import window, logging import PlexFunctions as pf @@ -11,7 +9,7 @@ from functions import * @logging class SubscriptionManager: - def __init__(self, jsonClass, RequestMgr): + def __init__(self, jsonClass, RequestMgr, player): self.serverlist = [] self.subscribers = {} self.info = {} @@ -25,7 +23,7 @@ class SubscriptionManager: self.port = "" self.playerprops = {} self.doUtils = downloadutils.DownloadUtils().downloadUrl - self.xbmcplayer = Player() + self.xbmcplayer = player self.js = jsonClass self.RequestMgr = RequestMgr diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 80f1e8df..2a020222 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -16,6 +16,7 @@ from functools import wraps from calendar import timegm import os + import xbmc import xbmcaddon import xbmcgui @@ -111,6 +112,19 @@ def IfExists(path): 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): """ Decorator for methods to catch exceptions and log them. Useful for e.g. diff --git a/service.py b/service.py index e2003332..76c3ffaf 100644 --- a/service.py +++ b/service.py @@ -4,7 +4,6 @@ import os import sys -from datetime import datetime import Queue import xbmc @@ -39,7 +38,6 @@ import clientinfo import initialsetup import kodimonitor import librarysync -import player import videonodes import websocket_client as wsc import downloadutils @@ -139,12 +137,8 @@ class Service(): user = userclient.UserClient() ws = wsc.WebSocket(queue) library = librarysync.LibrarySync(queue) - kplayer = player.Player() plx = PlexAPI.PlexAPI() - # Sync and progress report - lastProgressUpdate = datetime.today() - counter = 0 while not monitor.abortRequested(): @@ -153,9 +147,8 @@ class Service(): log("Kodi profile was: %s and changed to: %s. Terminating old " "PlexKodiConnect thread." % (kodiProfile, utils.window('plex_kodiProfile')), 1) - break - + # Before proceeding, need to make sure: # 1. Server is online # 2. User is set @@ -165,36 +158,6 @@ class Service(): # Plex server is online # Verify if user is set and has access to the server 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: # Start up events self.warn_auth = True @@ -208,8 +171,7 @@ class Service(): time=2000, sound=False) # Start monitoring kodi events - self.kodimonitor_running = kodimonitor.KodiMonitor( - player=kplayer) + self.kodimonitor_running = kodimonitor.KodiMonitor() # Start the Websocket Client if not self.websocket_running: @@ -222,8 +184,7 @@ class Service(): # Start the Plex Companion thread if not self.plexCompanion_running: self.plexCompanion_running = True - plexCompanion = PlexCompanion.PlexCompanion( - player=kplayer) + plexCompanion = PlexCompanion.PlexCompanion() plexCompanion.start() else: if (user.currUser is None) and self.warn_auth: