import threading import platform import uuid import sys import callback import signalsmixin import simpleobjects import nowplayingmanager import util Res = simpleobjects.Res APP = None INTERFACE = None MANAGER = None SERVERMANAGER = None ACCOUNT = None PLATFORM = util.X_PLEX_DEVICE def init(): global MANAGER, SERVERMANAGER, ACCOUNT import myplexaccount ACCOUNT = myplexaccount.ACCOUNT import plexservermanager SERVERMANAGER = plexservermanager.MANAGER import myplexmanager MANAGER = myplexmanager.MANAGER ACCOUNT.init() class App(signalsmixin.SignalsMixin): def __init__(self): signalsmixin.SignalsMixin.__init__(self) self.pendingRequests = {} self.initializers = {} self.timers = [] self.nowplayingmanager = nowplayingmanager.NowPlayingManager() def addTimer(self, timer): self.timers.append(timer) def startRequest(self, request, context, body=None, contentType=None): context.request = request started = request.startAsync(body=body, contentType=contentType, context=context) if started: requestID = context.request.getIdentity() self.pendingRequests[requestID] = context elif context.callback: context.callback(None, context) return started def onRequestTimeout(self, context): requestID = context.request.getIdentity() if requestID not in self.pendingRequests: return context.request.cancel() util.WARN_LOG("Request to {0} timed out after {1} sec".format(util.cleanToken(context.request.url), context.timeout)) if context.callback: context.callback(None, context) def delRequest(self, request): requestID = request.getIdentity() if requestID not in self.pendingRequests: return del self.pendingRequests[requestID] def addInitializer(self, name): self.initializers[name] = True def clearInitializer(self, name): if name in self.initializers: del self.initializers[name] if self.isInitialized(): self.onInitialized() def isInitialized(self): return not self.initializers def onInitialized(self): # Wire up a few of our own listeners # PlexServerManager() # self.on("change:user", callback.Callable(self.onAccountChange)) self.trigger('init') def cancelAllTimers(self): for timer in self.timers: timer.cancel() def preShutdown(self): import http http.HttpRequest._cancel = True if self.pendingRequests: util.DEBUG_LOG('Closing down {0} App() requests...'.format(len(self.pendingRequests))) for p in self.pendingRequests.values(): if p: p.request.cancel() if self.timers: util.DEBUG_LOG('Canceling App() timers...') self.cancelAllTimers() if SERVERMANAGER.selectedServer: util.DEBUG_LOG('Closing server...') SERVERMANAGER.selectedServer.close() def shutdown(self): if self.timers: util.DEBUG_LOG('Waiting for {0} App() timers: Started'.format(len(self.timers))) self.cancelAllTimers() for timer in self.timers: timer.join() util.DEBUG_LOG('Waiting for App() timers: Finished') class DeviceInfo(object): def getCaptionsOption(self, key): return None class AppInterface(object): QUALITY_LOCAL = 0 QUALITY_REMOTE = 1 QUALITY_ONLINE = 2 _globals = {} def __init__(self): self.setQualities() def setQualities(self): # Calculate the max quality based on 4k support if self._globals.get("supports4k"): maxQuality = simpleobjects.AttributeDict({ 'height': 2160, 'maxHeight': 2160, 'origHeight': 1080 }) maxResolution = self._globals.get("Is4k") and "4k" or "1080p" else: maxQuality = simpleobjects.AttributeDict({ 'height': 1080, 'maxHeight': 1088 }) maxResolution = "1080p" self._globals['qualities'] = [ simpleobjects.AttributeDict({'title': "Original", 'index': 13, 'maxBitrate': 200000}), simpleobjects.AttributeDict({'title': "20 Mbps " + maxResolution, 'index': 12, 'maxBitrate': 20000}), simpleobjects.AttributeDict({'title': "12 Mbps " + maxResolution, 'index': 11, 'maxBitrate': 12000}), simpleobjects.AttributeDict({'title': "10 Mbps " + maxResolution, 'index': 10, 'maxBitrate': 10000}), simpleobjects.AttributeDict({'title': "8 Mbps " + maxResolution, 'index': 9, 'maxBitrate': 8000}), simpleobjects.AttributeDict({'title': "4 Mbps 720p", 'index': 8, 'maxBitrate': 4000, 'maxHeight': 720}), simpleobjects.AttributeDict({'title': "3 Mbps 720p", 'index': 7, 'maxBitrate': 3000, 'maxHeight': 720}), simpleobjects.AttributeDict({'title': "2 Mbps 720p", 'index': 6, 'maxBitrate': 2000, 'maxHeight': 720}), simpleobjects.AttributeDict({'title': "1.5 Mbps 480p", 'index': 5, 'maxBitrate': 1500, 'maxHeight': 480}), simpleobjects.AttributeDict({'title': "720 Kbps", 'index': 4, 'maxBitrate': 720, 'maxHeight': 360}), simpleobjects.AttributeDict({'title': "320 Kbps", 'index': 3, 'maxBitrate': 320, 'maxHeight': 360}), maxQuality ] for quality in self._globals['qualities']: if quality.index >= 9: quality.update(maxQuality) def getPreference(self, pref, default=None): raise NotImplementedError def setPreference(self, pref, value): raise NotImplementedError def clearRegistry(self, reg, sec=None): raise NotImplementedError def getRegistry(self, reg, default=None, sec=None): raise NotImplementedError def setRegistry(self, reg, value, sec=None): raise NotImplementedError def getGlobal(self, glbl, default=None): raise NotImplementedError def getCapabilities(self): raise NotImplementedError def LOG(self, msg): raise NotImplementedError def DEBUG_LOG(self, msg): self.LOG(msg) def WARN_LOG(self, msg): self.LOG(msg) def ERROR_LOG(self, msg): self.LOG(msg) def ERROR(self, msg=None, err=None): self.LOG(msg) def FATAL(self, msg=None): self.ERROR_LOG('FATAL: {0}'.format(msg)) def supportsAudioStream(self, codec, channels): return False def supportsSurroundSound(self): return False def getMaxResolution(self, quality_type, allow4k=False): return 480 def getQualityIndex(self, qualityType): if qualityType == self.QUALITY_LOCAL: return self.getPreference("local_quality", 13) elif qualityType == self.QUALITY_ONLINE: return self.getPreference("online_quality", 8) else: return self.getPreference("remote_quality", 13) def settingsGetMaxResolution(self, qualityType, allow4k): qualityIndex = self.getQualityIndex(qualityType) if qualityIndex >= 9: return allow4k and 2160 or 1088 elif qualityIndex >= 6: return 720 elif qualityIndex >= 5: return 480 else: return 360 def getMaxBitrate(self, qualityType): qualityIndex = self.getQualityIndex(qualityType) qualities = self.getGlobal("qualities", []) for quality in qualities: if quality.index == qualityIndex: return util.validInt(quality.maxBitrate) return 0 class PlayerSettingsInterface(object): def __init__(self): self.prefOverrides = {} def __getattr__(self, name): return getattr(INTERFACE, name) def setPrefOverride(self, key, val): self.prefOverrides[key] = val def getPrefOverride(self, key, default=None): return self.prefOverrides.get(key, default) def getQualityIndex(self, qualityType): if qualityType == INTERFACE.QUALITY_LOCAL: return self.getPreference("local_quality", 13) elif qualityType == INTERFACE.QUALITY_ONLINE: return self.getPreference("online_quality", 8) else: return self.getPreference("remote_quality", 13) def getPreference(self, key, default=None): if key in self.prefOverrides: return self.prefOverrides[key] else: return INTERFACE.getPreference(key, default) def getMaxResolution(self, quality_type, allow4k=False): qualityIndex = self.getQualityIndex(quality_type) if qualityIndex >= 9: return allow4k and 2160 or 1088 elif qualityIndex >= 6: return 720 elif qualityIndex >= 5: return 480 else: return 360 class DumbInterface(AppInterface): _prefs = {} _regs = { None: {} } _globals = { 'platform': platform.uname()[0], 'appVersionStr': '0.0.0a1', 'clientIdentifier': str(hex(uuid.getnode())), 'platformVersion': platform.uname()[2], 'product': 'PlexNet.API', 'provides': 'player', 'device': platform.uname()[0], 'model': 'Unknown', 'friendlyName': 'PlexNet.API', 'deviceInfo': DeviceInfo() } def getPreference(self, pref, default=None): return self._prefs.get(pref, default) def setPreference(self, pref, value): self._prefs[pref] = value def getRegistry(self, reg, default=None, sec=None): section = self._regs.get(sec) if section: return section.get(reg, default) return default def setRegistry(self, reg, value, sec=None): if sec and sec not in self._regs: self._regs[sec] = {} self._regs[sec][reg] = value def clearRegistry(self, reg, sec=None): del self._regs[sec][reg] def getGlobal(self, glbl, default=None): return self._globals.get(glbl, default) def getCapabilities(self): return '' def LOG(self, msg): print 'PlexNet.API: {0}'.format(msg) def DEBUG_LOG(self, msg): self.LOG('DEBUG: {0}'.format(msg)) def WARN_LOG(self, msg): self.LOG('WARNING: {0}'.format(msg)) def ERROR_LOG(self, msg): self.LOG('ERROR: {0}'.format(msg)) def ERROR(self, msg=None, err=None): if err: self.LOG('ERROR: {0} - {1}'.format(msg, err.message)) else: import traceback traceback.print_exc() class CompatEvent(threading._Event): def wait(self, timeout): threading._Event.wait(self, timeout) return self.isSet() class Timer(object): def __init__(self, timeout, function, repeat=False, *args, **kwargs): self.function = function self.timeout = timeout self.repeat = repeat self.args = args self.kwargs = kwargs self._reset = False self.event = CompatEvent() self.start() def start(self): self.event.clear() self.thread = threading.Thread(target=self.run, name='TIMER:{0}'.format(self.function), *self.args, **self.kwargs) self.thread.start() def run(self): util.DEBUG_LOG('Timer {0}: {1}'.format(repr(self.function), self._reset and 'RESET'or 'STARTED')) try: while not self.event.isSet() and not self.shouldAbort(): while not self.event.wait(self.timeout) and not self.shouldAbort(): if self._reset: return self.function(*self.args, **self.kwargs) if not self.repeat: return finally: if not self._reset: if self in APP.timers: APP.timers.remove(self) util.DEBUG_LOG('Timer {0}: FINISHED'.format(repr(self.function))) self._reset = False def cancel(self): self.event.set() def reset(self): self._reset = True self.cancel() if self.thread and self.thread.isAlive(): self.thread.join() self.start() def shouldAbort(self): return False def join(self): if self.thread.isAlive(): self.thread.join() def isExpired(self): return self.event.isSet() TIMER = Timer def createTimer(timeout, function, repeat=False, *args, **kwargs): if isinstance(function, basestring): def dummy(*args, **kwargs): pass dummy.__name__ = function function = dummy timer = TIMER(timeout / 1000.0, function, repeat=repeat, *args, **kwargs) return timer def setTimer(timer): global TIMER TIMER = timer def setInterface(interface): global INTERFACE INTERFACE = interface def setApp(app): global APP APP = app def setUserAgent(agent): util.USER_AGENT = agent util.BASE_HEADERS = util.resetBaseHeaders() def setAbortFlagFunction(func): import asyncadapter asyncadapter.ABORT_FLAG_FUNCTION = func def refreshResources(force=False): import gdm gdm.DISCOVERY.discover() MANAGER.refreshResources(force) SERVERMANAGER.refreshManualConnections() setApp(App()) setInterface(DumbInterface())