Optimize player and playlist instances
This commit is contained in:
parent
ee802428f9
commit
a2693b3485
8 changed files with 71 additions and 212 deletions
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
45
service.py
45
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:
|
||||
|
|
Loading…
Reference in a new issue