Attempt to fix locking mechanisms
- Wraper to lock entire function was NOT working
This commit is contained in:
parent
c440dc7779
commit
9b76795ea4
7 changed files with 183 additions and 204 deletions
|
@ -138,7 +138,8 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
|
|
||||||
if method == "Player.OnPlay":
|
if method == "Player.OnPlay":
|
||||||
state.SUSPEND_SYNC = True
|
state.SUSPEND_SYNC = True
|
||||||
self.PlayBackStart(data)
|
with state.LOCK_PLAYQUEUES:
|
||||||
|
self.PlayBackStart(data)
|
||||||
elif method == "Player.OnStop":
|
elif method == "Player.OnStop":
|
||||||
# Should refresh our video nodes, e.g. on deck
|
# Should refresh our video nodes, e.g. on deck
|
||||||
# xbmc.executebuiltin('ReloadSkin()')
|
# xbmc.executebuiltin('ReloadSkin()')
|
||||||
|
@ -146,23 +147,28 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
self.hack_replay == data['item']):
|
self.hack_replay == data['item']):
|
||||||
# Hack for add-on paths
|
# Hack for add-on paths
|
||||||
self.hack_replay = None
|
self.hack_replay = None
|
||||||
self._hack_addon_paths_replay_video()
|
with state.LOCK_PLAYQUEUES:
|
||||||
|
self._hack_addon_paths_replay_video()
|
||||||
elif data.get('end'):
|
elif data.get('end'):
|
||||||
if state.PKC_CAUSED_STOP is True:
|
if state.PKC_CAUSED_STOP is True:
|
||||||
state.PKC_CAUSED_STOP = False
|
state.PKC_CAUSED_STOP = False
|
||||||
LOG.debug('PKC caused this playback stop - ignoring')
|
LOG.debug('PKC caused this playback stop - ignoring')
|
||||||
else:
|
else:
|
||||||
_playback_cleanup(ended=True)
|
with state.LOCK_PLAYQUEUES:
|
||||||
|
_playback_cleanup(ended=True)
|
||||||
else:
|
else:
|
||||||
_playback_cleanup()
|
with state.LOCK_PLAYQUEUES:
|
||||||
|
_playback_cleanup()
|
||||||
state.PKC_CAUSED_STOP_DONE = True
|
state.PKC_CAUSED_STOP_DONE = True
|
||||||
state.SUSPEND_SYNC = False
|
state.SUSPEND_SYNC = False
|
||||||
elif method == 'Playlist.OnAdd':
|
elif method == 'Playlist.OnAdd':
|
||||||
self._playlist_onadd(data)
|
with state.LOCK_PLAYQUEUES:
|
||||||
|
self._playlist_onadd(data)
|
||||||
elif method == 'Playlist.OnRemove':
|
elif method == 'Playlist.OnRemove':
|
||||||
self._playlist_onremove(data)
|
self._playlist_onremove(data)
|
||||||
elif method == 'Playlist.OnClear':
|
elif method == 'Playlist.OnClear':
|
||||||
self._playlist_onclear(data)
|
with state.LOCK_PLAYQUEUES:
|
||||||
|
self._playlist_onclear(data)
|
||||||
elif method == "VideoLibrary.OnUpdate":
|
elif method == "VideoLibrary.OnUpdate":
|
||||||
# Manually marking as watched/unwatched
|
# Manually marking as watched/unwatched
|
||||||
playcount = data.get('playcount')
|
playcount = data.get('playcount')
|
||||||
|
@ -208,7 +214,6 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
state.STOP_PKC = True
|
state.STOP_PKC = True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@state.LOCKER_SUBSCRIBER.lockthis
|
|
||||||
def _hack_addon_paths_replay_video():
|
def _hack_addon_paths_replay_video():
|
||||||
"""
|
"""
|
||||||
Hack we need for RESUMABLE items because Kodi lost the path of the
|
Hack we need for RESUMABLE items because Kodi lost the path of the
|
||||||
|
@ -239,7 +244,6 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
thread = Thread(target=playback.playback_triage, kwargs=kwargs)
|
thread = Thread(target=playback.playback_triage, kwargs=kwargs)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
@state.LOCKER_SUBSCRIBER.lockthis
|
|
||||||
def _playlist_onadd(self, data):
|
def _playlist_onadd(self, data):
|
||||||
"""
|
"""
|
||||||
Called if an item is added to a Kodi playlist. Example data dict:
|
Called if an item is added to a Kodi playlist. Example data dict:
|
||||||
|
@ -272,7 +276,6 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@state.LOCKER_SUBSCRIBER.lockthis
|
|
||||||
def _playlist_onclear(self, data):
|
def _playlist_onclear(self, data):
|
||||||
"""
|
"""
|
||||||
Called if a Kodi playlist is cleared. Example data dict:
|
Called if a Kodi playlist is cleared. Example data dict:
|
||||||
|
@ -348,7 +351,6 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
json_item.get('type'),
|
json_item.get('type'),
|
||||||
json_item.get('file'))
|
json_item.get('file'))
|
||||||
|
|
||||||
@state.LOCKER_SUBSCRIBER.lockthis
|
|
||||||
def PlayBackStart(self, data):
|
def PlayBackStart(self, data):
|
||||||
"""
|
"""
|
||||||
Called whenever playback is started. Example data:
|
Called whenever playback is started. Example data:
|
||||||
|
@ -486,7 +488,6 @@ class SpecialMonitor(Thread):
|
||||||
LOG.info("#====---- Special Monitor Stopped ----====#")
|
LOG.info("#====---- Special Monitor Stopped ----====#")
|
||||||
|
|
||||||
|
|
||||||
@state.LOCKER_SUBSCRIBER.lockthis
|
|
||||||
def _playback_cleanup(ended=False):
|
def _playback_cleanup(ended=False):
|
||||||
"""
|
"""
|
||||||
PKC cleanup after playback ends/is stopped. Pass ended=True if Kodi
|
PKC cleanup after playback ends/is stopped. Pass ended=True if Kodi
|
||||||
|
|
|
@ -30,7 +30,6 @@ NULL_VIDEO = join(v.ADDON_FOLDER, 'addons', v.ADDON_ID, 'empty_video.mp4')
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
@state.LOCKER_SUBSCRIBER.lockthis
|
|
||||||
def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True):
|
def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True):
|
||||||
"""
|
"""
|
||||||
Hit this function for addon path playback, Plex trailers, etc.
|
Hit this function for addon path playback, Plex trailers, etc.
|
||||||
|
@ -107,11 +106,12 @@ def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True):
|
||||||
initiate = True
|
initiate = True
|
||||||
else:
|
else:
|
||||||
initiate = False
|
initiate = False
|
||||||
if initiate:
|
with state.LOCK_PLAYQUEUES:
|
||||||
_playback_init(plex_id, plex_type, playqueue, pos)
|
if initiate:
|
||||||
else:
|
_playback_init(plex_id, plex_type, playqueue, pos)
|
||||||
# kick off playback on second pass
|
else:
|
||||||
_conclude_playback(playqueue, pos)
|
# kick off playback on second pass
|
||||||
|
_conclude_playback(playqueue, pos)
|
||||||
|
|
||||||
|
|
||||||
def _playlist_playback(plex_id, plex_type):
|
def _playlist_playback(plex_id, plex_type):
|
||||||
|
|
|
@ -267,41 +267,49 @@ def _kodi_playlist_identical(xml_element):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@state.LOCKER_PLAYLISTS.lockthis
|
|
||||||
def process_websocket(plex_id, updated_at, state):
|
def process_websocket(plex_id, updated_at, state):
|
||||||
"""
|
"""
|
||||||
Hit by librarysync to process websocket messages concerning playlists
|
Hit by librarysync to process websocket messages concerning playlists
|
||||||
"""
|
"""
|
||||||
create = False
|
create = False
|
||||||
playlist = playlist_object_from_db(plex_id=plex_id)
|
playlist = playlist_object_from_db(plex_id=plex_id)
|
||||||
try:
|
with state.LOCK_PLAYLISTS:
|
||||||
if playlist and state == 9:
|
try:
|
||||||
LOG.debug('Plex deletion of playlist detected: %s', playlist)
|
if playlist and state == 9:
|
||||||
delete_kodi_playlist(playlist)
|
LOG.debug('Plex deletion of playlist detected: %s', playlist)
|
||||||
elif playlist and playlist.plex_updatedat == updated_at:
|
delete_kodi_playlist(playlist)
|
||||||
LOG.debug('Playlist with id %s already synced: %s',
|
elif playlist and playlist.plex_updatedat == updated_at:
|
||||||
plex_id, playlist)
|
LOG.debug('Playlist with id %s already synced: %s',
|
||||||
elif playlist:
|
plex_id, playlist)
|
||||||
LOG.debug('Change of Plex playlist detected: %s', playlist)
|
elif playlist:
|
||||||
delete_kodi_playlist(playlist)
|
LOG.debug('Change of Plex playlist detected: %s', playlist)
|
||||||
create = True
|
delete_kodi_playlist(playlist)
|
||||||
elif not playlist and not state == 9:
|
create = True
|
||||||
LOG.debug('Creation of new Plex playlist detected: %s', plex_id)
|
elif not playlist and not state == 9:
|
||||||
create = True
|
LOG.debug('Creation of new Plex playlist detected: %s',
|
||||||
# To the actual work
|
plex_id)
|
||||||
if create:
|
create = True
|
||||||
create_kodi_playlist(plex_id=plex_id, updated_at=updated_at)
|
# To the actual work
|
||||||
except PL.PlaylistError:
|
if create:
|
||||||
pass
|
create_kodi_playlist(plex_id=plex_id, updated_at=updated_at)
|
||||||
|
except PL.PlaylistError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@state.LOCKER_PLAYLISTS.lockthis
|
|
||||||
def full_sync():
|
def full_sync():
|
||||||
"""
|
"""
|
||||||
Full sync of playlists between Kodi and Plex. Returns True is successful,
|
Full sync of playlists between Kodi and Plex. Returns True is successful,
|
||||||
False otherwise
|
False otherwise
|
||||||
"""
|
"""
|
||||||
LOG.info('Starting playlist full sync')
|
LOG.info('Starting playlist full sync')
|
||||||
|
with state.LOCK_PLAYLISTS:
|
||||||
|
return _full_sync()
|
||||||
|
|
||||||
|
|
||||||
|
def _full_sync():
|
||||||
|
"""
|
||||||
|
Need to lock because we're messing with playlists
|
||||||
|
"""
|
||||||
# Get all Plex playlists
|
# Get all Plex playlists
|
||||||
xml = PL.get_all_playlists()
|
xml = PL.get_all_playlists()
|
||||||
if xml is None:
|
if xml is None:
|
||||||
|
|
|
@ -34,7 +34,7 @@ def init_playqueues():
|
||||||
LOG.debug('Playqueues have already been initialized')
|
LOG.debug('Playqueues have already been initialized')
|
||||||
return
|
return
|
||||||
# Initialize Kodi playqueues
|
# Initialize Kodi playqueues
|
||||||
with state.LOCK_SUBSCRIBER:
|
with state.LOCK_PLAYQUEUES:
|
||||||
for i in (0, 1, 2):
|
for i in (0, 1, 2):
|
||||||
# Just in case the Kodi response is not sorted correctly
|
# Just in case the Kodi response is not sorted correctly
|
||||||
for queue in js.get_playlists():
|
for queue in js.get_playlists():
|
||||||
|
@ -62,14 +62,13 @@ def get_playqueue_from_type(kodi_playlist_type):
|
||||||
Returns the playqueue according to the kodi_playlist_type ('video',
|
Returns the playqueue according to the kodi_playlist_type ('video',
|
||||||
'audio', 'picture') passed in
|
'audio', 'picture') passed in
|
||||||
"""
|
"""
|
||||||
with state.LOCK_SUBSCRIBER:
|
for playqueue in PLAYQUEUES:
|
||||||
for playqueue in PLAYQUEUES:
|
if playqueue.type == kodi_playlist_type:
|
||||||
if playqueue.type == kodi_playlist_type:
|
break
|
||||||
break
|
else:
|
||||||
else:
|
raise ValueError('Wrong playlist type passed in: %s',
|
||||||
raise ValueError('Wrong playlist type passed in: %s',
|
kodi_playlist_type)
|
||||||
kodi_playlist_type)
|
return playqueue
|
||||||
return playqueue
|
|
||||||
|
|
||||||
|
|
||||||
def init_playqueue_from_plex_children(plex_id, transient_token=None):
|
def init_playqueue_from_plex_children(plex_id, transient_token=None):
|
||||||
|
@ -190,7 +189,7 @@ class PlayqueueMonitor(Thread):
|
||||||
if stopped():
|
if stopped():
|
||||||
break
|
break
|
||||||
xbmc.sleep(1000)
|
xbmc.sleep(1000)
|
||||||
with state.LOCK_SUBSCRIBER:
|
with state.LOCK_PLAYQUEUES:
|
||||||
for playqueue in PLAYQUEUES:
|
for playqueue in PLAYQUEUES:
|
||||||
kodi_pl = js.playlist_get_items(playqueue.playlistid)
|
kodi_pl = js.playlist_get_items(playqueue.playlistid)
|
||||||
if playqueue.old_kodi_pl != kodi_pl:
|
if playqueue.old_kodi_pl != kodi_pl:
|
||||||
|
|
|
@ -43,7 +43,7 @@ def update_playqueue_from_PMS(playqueue,
|
||||||
# Safe transient token from being deleted
|
# Safe transient token from being deleted
|
||||||
if transient_token is None:
|
if transient_token is None:
|
||||||
transient_token = playqueue.plex_transient_token
|
transient_token = playqueue.plex_transient_token
|
||||||
with state.LOCK_SUBSCRIBER:
|
with state.LOCK_PLAYQUEUES:
|
||||||
xml = PL.get_PMS_playlist(playqueue, playqueue_id)
|
xml = PL.get_PMS_playlist(playqueue, playqueue_id)
|
||||||
playqueue.clear()
|
playqueue.clear()
|
||||||
try:
|
try:
|
||||||
|
@ -74,7 +74,6 @@ class PlexCompanion(Thread):
|
||||||
self.subscription_manager = None
|
self.subscription_manager = None
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
|
|
||||||
@state.LOCKER_SUBSCRIBER.lockthis
|
|
||||||
def _process_alexa(self, data):
|
def _process_alexa(self, data):
|
||||||
xml = PF.GetPlexMetadata(data['key'])
|
xml = PF.GetPlexMetadata(data['key'])
|
||||||
try:
|
try:
|
||||||
|
@ -131,7 +130,6 @@ class PlexCompanion(Thread):
|
||||||
executebuiltin('RunPlugin(plugin://%s?%s)'
|
executebuiltin('RunPlugin(plugin://%s?%s)'
|
||||||
% (v.ADDON_ID, urlencode(params)))
|
% (v.ADDON_ID, urlencode(params)))
|
||||||
|
|
||||||
@state.LOCKER_SUBSCRIBER.lockthis
|
|
||||||
def _process_playlist(self, data):
|
def _process_playlist(self, data):
|
||||||
# Get the playqueue ID
|
# Get the playqueue ID
|
||||||
_, container_key, query = PF.ParseContainerKey(data['containerKey'])
|
_, container_key, query = PF.ParseContainerKey(data['containerKey'])
|
||||||
|
@ -156,7 +154,6 @@ class PlexCompanion(Thread):
|
||||||
offset=data.get('offset'),
|
offset=data.get('offset'),
|
||||||
transient_token=data.get('token'))
|
transient_token=data.get('token'))
|
||||||
|
|
||||||
@state.LOCKER_SUBSCRIBER.lockthis
|
|
||||||
def _process_streams(self, data):
|
def _process_streams(self, data):
|
||||||
"""
|
"""
|
||||||
Plex Companion client adjusted audio or subtitle stream
|
Plex Companion client adjusted audio or subtitle stream
|
||||||
|
@ -178,7 +175,6 @@ class PlexCompanion(Thread):
|
||||||
else:
|
else:
|
||||||
LOG.error('Unknown setStreams command: %s', data)
|
LOG.error('Unknown setStreams command: %s', data)
|
||||||
|
|
||||||
@state.LOCKER_SUBSCRIBER.lockthis
|
|
||||||
def _process_refresh(self, data):
|
def _process_refresh(self, data):
|
||||||
"""
|
"""
|
||||||
example data: {'playQueueID': '8475', 'commandID': '11'}
|
example data: {'playQueueID': '8475', 'commandID': '11'}
|
||||||
|
@ -217,14 +213,17 @@ class PlexCompanion(Thread):
|
||||||
LOG.debug('Processing: %s', task)
|
LOG.debug('Processing: %s', task)
|
||||||
data = task['data']
|
data = task['data']
|
||||||
if task['action'] == 'alexa':
|
if task['action'] == 'alexa':
|
||||||
self._process_alexa(data)
|
with state.LOCK_PLAYQUEUES:
|
||||||
|
self._process_alexa(data)
|
||||||
elif (task['action'] == 'playlist' and
|
elif (task['action'] == 'playlist' and
|
||||||
data.get('address') == 'node.plexapp.com'):
|
data.get('address') == 'node.plexapp.com'):
|
||||||
self._process_node(data)
|
self._process_node(data)
|
||||||
elif task['action'] == 'playlist':
|
elif task['action'] == 'playlist':
|
||||||
self._process_playlist(data)
|
with state.LOCK_PLAYQUEUES:
|
||||||
|
self._process_playlist(data)
|
||||||
elif task['action'] == 'refreshPlayQueue':
|
elif task['action'] == 'refreshPlayQueue':
|
||||||
self._process_refresh(data)
|
with state.LOCK_PLAYQUEUES:
|
||||||
|
self._process_refresh(data)
|
||||||
elif task['action'] == 'setStreams':
|
elif task['action'] == 'setStreams':
|
||||||
try:
|
try:
|
||||||
self._process_streams(data)
|
self._process_streams(data)
|
||||||
|
|
|
@ -145,7 +145,6 @@ class SubscriptionMgr(object):
|
||||||
position = info['position']
|
position = info['position']
|
||||||
return position
|
return position
|
||||||
|
|
||||||
@state.LOCKER_SUBSCRIBER.lockthis
|
|
||||||
def msg(self, players):
|
def msg(self, players):
|
||||||
"""
|
"""
|
||||||
Returns a timeline xml as str
|
Returns a timeline xml as str
|
||||||
|
@ -185,94 +184,98 @@ class SubscriptionMgr(object):
|
||||||
return answ
|
return answ
|
||||||
|
|
||||||
def _timeline_dict(self, player, ptype):
|
def _timeline_dict(self, player, ptype):
|
||||||
playerid = player['playerid']
|
with state.LOCK_PLAYQUEUES:
|
||||||
info = state.PLAYER_STATES[playerid]
|
playerid = player['playerid']
|
||||||
playqueue = PQ.PLAYQUEUES[playerid]
|
info = state.PLAYER_STATES[playerid]
|
||||||
position = self._get_correct_position(info, playqueue)
|
playqueue = PQ.PLAYQUEUES[playerid]
|
||||||
try:
|
position = self._get_correct_position(info, playqueue)
|
||||||
item = playqueue.items[position]
|
try:
|
||||||
except IndexError:
|
item = playqueue.items[position]
|
||||||
# E.g. for direct path playback for single item
|
except IndexError:
|
||||||
return {
|
# E.g. for direct path playback for single item
|
||||||
|
return {
|
||||||
|
'controllable': CONTROLLABLE[ptype],
|
||||||
|
'type': ptype,
|
||||||
|
'state': 'stopped'
|
||||||
|
}
|
||||||
|
if ptype in (v.PLEX_PLAYLIST_TYPE_VIDEO,
|
||||||
|
v.PLEX_PLAYLIST_TYPE_PHOTO):
|
||||||
|
self.location = 'fullScreenVideo'
|
||||||
|
self.stop_sent_to_web = False
|
||||||
|
pbmc_server = utils.window('pms_server')
|
||||||
|
if pbmc_server:
|
||||||
|
(self.protocol, self.server, self.port) = pbmc_server.split(':')
|
||||||
|
self.server = self.server.replace('/', '')
|
||||||
|
status = 'paused' if int(info['speed']) == 0 else 'playing'
|
||||||
|
duration = utils.kodi_time_to_millis(info['totaltime'])
|
||||||
|
shuffle = '1' if info['shuffled'] else '0'
|
||||||
|
mute = '1' if info['muted'] is True else '0'
|
||||||
|
answ = {
|
||||||
'controllable': CONTROLLABLE[ptype],
|
'controllable': CONTROLLABLE[ptype],
|
||||||
|
'protocol': self.protocol,
|
||||||
|
'address': self.server,
|
||||||
|
'port': self.port,
|
||||||
|
'machineIdentifier': utils.window('plex_machineIdentifier'),
|
||||||
|
'state': status,
|
||||||
'type': ptype,
|
'type': ptype,
|
||||||
'state': 'stopped'
|
'itemType': ptype,
|
||||||
|
'time': utils.kodi_time_to_millis(info['time']),
|
||||||
|
'duration': duration,
|
||||||
|
'seekRange': '0-%s' % duration,
|
||||||
|
'shuffle': shuffle,
|
||||||
|
'repeat': v.PLEX_REPEAT_FROM_KODI_REPEAT[info['repeat']],
|
||||||
|
'volume': info['volume'],
|
||||||
|
'mute': mute,
|
||||||
|
'mediaIndex': 0, # Still to implement from here
|
||||||
|
'partIndex': 0,
|
||||||
|
'partCount': 1,
|
||||||
|
'providerIdentifier': 'com.plexapp.plugins.library',
|
||||||
}
|
}
|
||||||
if ptype in (v.PLEX_PLAYLIST_TYPE_VIDEO, v.PLEX_PLAYLIST_TYPE_PHOTO):
|
# Get the plex id from the PKC playqueue not info, as Kodi jumps to
|
||||||
self.location = 'fullScreenVideo'
|
# next playqueue element way BEFORE kodi monitor onplayback is
|
||||||
self.stop_sent_to_web = False
|
# called
|
||||||
pbmc_server = utils.window('pms_server')
|
if item.plex_id:
|
||||||
if pbmc_server:
|
answ['key'] = '/library/metadata/%s' % item.plex_id
|
||||||
(self.protocol, self.server, self.port) = pbmc_server.split(':')
|
answ['ratingKey'] = item.plex_id
|
||||||
self.server = self.server.replace('/', '')
|
# PlayQueue stuff
|
||||||
status = 'paused' if int(info['speed']) == 0 else 'playing'
|
if info['container_key']:
|
||||||
duration = utils.kodi_time_to_millis(info['totaltime'])
|
answ['containerKey'] = info['container_key']
|
||||||
shuffle = '1' if info['shuffled'] else '0'
|
if (info['container_key'] is not None and
|
||||||
mute = '1' if info['muted'] is True else '0'
|
info['container_key'].startswith('/playQueues')):
|
||||||
answ = {
|
answ['playQueueID'] = playqueue.id
|
||||||
'controllable': CONTROLLABLE[ptype],
|
answ['playQueueVersion'] = playqueue.version
|
||||||
'protocol': self.protocol,
|
answ['playQueueItemID'] = item.id
|
||||||
'address': self.server,
|
if playqueue.items[position].guid:
|
||||||
'port': self.port,
|
answ['guid'] = item.guid
|
||||||
'machineIdentifier': utils.window('plex_machineIdentifier'),
|
# Temp. token set?
|
||||||
'state': status,
|
if state.PLEX_TRANSIENT_TOKEN:
|
||||||
'type': ptype,
|
answ['token'] = state.PLEX_TRANSIENT_TOKEN
|
||||||
'itemType': ptype,
|
elif playqueue.plex_transient_token:
|
||||||
'time': utils.kodi_time_to_millis(info['time']),
|
answ['token'] = playqueue.plex_transient_token
|
||||||
'duration': duration,
|
# Process audio and subtitle streams
|
||||||
'seekRange': '0-%s' % duration,
|
if ptype == v.PLEX_PLAYLIST_TYPE_VIDEO:
|
||||||
'shuffle': shuffle,
|
strm_id = self._plex_stream_index(playerid, 'audio')
|
||||||
'repeat': v.PLEX_REPEAT_FROM_KODI_REPEAT[info['repeat']],
|
if strm_id:
|
||||||
'volume': info['volume'],
|
answ['audioStreamID'] = strm_id
|
||||||
'mute': mute,
|
else:
|
||||||
'mediaIndex': 0, # Still to implement from here
|
LOG.error('We could not select a Plex audiostream')
|
||||||
'partIndex': 0,
|
strm_id = self._plex_stream_index(playerid, 'video')
|
||||||
'partCount': 1,
|
if strm_id:
|
||||||
'providerIdentifier': 'com.plexapp.plugins.library',
|
answ['videoStreamID'] = strm_id
|
||||||
}
|
else:
|
||||||
# Get the plex id from the PKC playqueue not info, as Kodi jumps to next
|
LOG.error('We could not select a Plex videostream')
|
||||||
# playqueue element way BEFORE kodi monitor onplayback is called
|
if info['subtitleenabled']:
|
||||||
if item.plex_id:
|
try:
|
||||||
answ['key'] = '/library/metadata/%s' % item.plex_id
|
strm_id = self._plex_stream_index(playerid, 'subtitle')
|
||||||
answ['ratingKey'] = item.plex_id
|
except KeyError:
|
||||||
# PlayQueue stuff
|
# subtitleenabled can be True while currentsubtitle can
|
||||||
if info['container_key']:
|
# still be {}
|
||||||
answ['containerKey'] = info['container_key']
|
strm_id = None
|
||||||
if (info['container_key'] is not None and
|
if strm_id is not None:
|
||||||
info['container_key'].startswith('/playQueues')):
|
# If None, then the subtitle is only present on Kodi
|
||||||
answ['playQueueID'] = playqueue.id
|
# side
|
||||||
answ['playQueueVersion'] = playqueue.version
|
answ['subtitleStreamID'] = strm_id
|
||||||
answ['playQueueItemID'] = item.id
|
return answ
|
||||||
if playqueue.items[position].guid:
|
|
||||||
answ['guid'] = item.guid
|
|
||||||
# Temp. token set?
|
|
||||||
if state.PLEX_TRANSIENT_TOKEN:
|
|
||||||
answ['token'] = state.PLEX_TRANSIENT_TOKEN
|
|
||||||
elif playqueue.plex_transient_token:
|
|
||||||
answ['token'] = playqueue.plex_transient_token
|
|
||||||
# Process audio and subtitle streams
|
|
||||||
if ptype == v.PLEX_PLAYLIST_TYPE_VIDEO:
|
|
||||||
strm_id = self._plex_stream_index(playerid, 'audio')
|
|
||||||
if strm_id:
|
|
||||||
answ['audioStreamID'] = strm_id
|
|
||||||
else:
|
|
||||||
LOG.error('We could not select a Plex audiostream')
|
|
||||||
strm_id = self._plex_stream_index(playerid, 'video')
|
|
||||||
if strm_id:
|
|
||||||
answ['videoStreamID'] = strm_id
|
|
||||||
else:
|
|
||||||
LOG.error('We could not select a Plex videostream')
|
|
||||||
if info['subtitleenabled']:
|
|
||||||
try:
|
|
||||||
strm_id = self._plex_stream_index(playerid, 'subtitle')
|
|
||||||
except KeyError:
|
|
||||||
# subtitleenabled can be True while currentsubtitle can
|
|
||||||
# still be {}
|
|
||||||
strm_id = None
|
|
||||||
if strm_id is not None:
|
|
||||||
# If None, then the subtitle is only present on Kodi side
|
|
||||||
answ['subtitleStreamID'] = strm_id
|
|
||||||
return answ
|
|
||||||
|
|
||||||
def signal_stop(self):
|
def signal_stop(self):
|
||||||
"""
|
"""
|
||||||
|
@ -297,14 +300,14 @@ class SubscriptionMgr(object):
|
||||||
return playqueue.items[position].plex_stream_index(
|
return playqueue.items[position].plex_stream_index(
|
||||||
info[STREAM_DETAILS[stream_type]]['index'], stream_type)
|
info[STREAM_DETAILS[stream_type]]['index'], stream_type)
|
||||||
|
|
||||||
@state.LOCKER_SUBSCRIBER.lockthis
|
|
||||||
def update_command_id(self, uuid, command_id):
|
def update_command_id(self, uuid, command_id):
|
||||||
"""
|
"""
|
||||||
Updates the Plex Companien client with the machine identifier uuid with
|
Updates the Plex Companien client with the machine identifier uuid with
|
||||||
command_id
|
command_id
|
||||||
"""
|
"""
|
||||||
if command_id and self.subscribers.get(uuid):
|
with state.LOCK_SUBSCRIBER:
|
||||||
self.subscribers[uuid].command_id = int(command_id)
|
if command_id and self.subscribers.get(uuid):
|
||||||
|
self.subscribers[uuid].command_id = int(command_id)
|
||||||
|
|
||||||
def _playqueue_init_done(self, players):
|
def _playqueue_init_done(self, players):
|
||||||
"""
|
"""
|
||||||
|
@ -327,29 +330,29 @@ class SubscriptionMgr(object):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@state.LOCKER_SUBSCRIBER.lockthis
|
|
||||||
def notify(self):
|
def notify(self):
|
||||||
"""
|
"""
|
||||||
Causes PKC to tell the PMS and Plex Companion players to receive a
|
Causes PKC to tell the PMS and Plex Companion players to receive a
|
||||||
notification what's being played.
|
notification what's being played.
|
||||||
"""
|
"""
|
||||||
self._cleanup()
|
with state.LOCK_SUBSCRIBER:
|
||||||
# Get all the active/playing Kodi players (video, audio, pictures)
|
self._cleanup()
|
||||||
players = js.get_players()
|
# Get all the active/playing Kodi players (video, audio, pictures)
|
||||||
# Update the PKC info with what's playing on the Kodi side
|
players = js.get_players()
|
||||||
for player in players.values():
|
# Update the PKC info with what's playing on the Kodi side
|
||||||
update_player_info(player['playerid'])
|
for player in players.values():
|
||||||
# Check whether we can use the CURRENT info or whether PKC is still
|
update_player_info(player['playerid'])
|
||||||
# initializing
|
# Check whether we can use the CURRENT info or whether PKC is still
|
||||||
if self._playqueue_init_done(players) is False:
|
# initializing
|
||||||
LOG.debug('PKC playqueue is still initializing - skipping update')
|
if self._playqueue_init_done(players) is False:
|
||||||
return
|
LOG.debug('PKC playqueue is still initializing - skip update')
|
||||||
self._notify_server(players)
|
return
|
||||||
if self.subscribers:
|
self._notify_server(players)
|
||||||
msg = self.msg(players)
|
if self.subscribers:
|
||||||
for subscriber in self.subscribers.values():
|
msg = self.msg(players)
|
||||||
subscriber.send_update(msg)
|
for subscriber in self.subscribers.values():
|
||||||
self.lastplayers = players
|
subscriber.send_update(msg)
|
||||||
|
self.lastplayers = players
|
||||||
|
|
||||||
def _notify_server(self, players):
|
def _notify_server(self, players):
|
||||||
for typus, player in players.iteritems():
|
for typus, player in players.iteritems():
|
||||||
|
@ -410,7 +413,6 @@ class SubscriptionMgr(object):
|
||||||
LOG.debug("Sent server notification with parameters: %s to %s",
|
LOG.debug("Sent server notification with parameters: %s to %s",
|
||||||
xargs, url)
|
xargs, url)
|
||||||
|
|
||||||
@state.LOCKER_SUBSCRIBER.lockthis
|
|
||||||
def add_subscriber(self, protocol, host, port, uuid, command_id):
|
def add_subscriber(self, protocol, host, port, uuid, command_id):
|
||||||
"""
|
"""
|
||||||
Adds a new Plex Companion subscriber to PKC.
|
Adds a new Plex Companion subscriber to PKC.
|
||||||
|
@ -422,20 +424,21 @@ class SubscriptionMgr(object):
|
||||||
command_id,
|
command_id,
|
||||||
self,
|
self,
|
||||||
self.request_mgr)
|
self.request_mgr)
|
||||||
self.subscribers[subscriber.uuid] = subscriber
|
with state.LOCK_SUBSCRIBER:
|
||||||
|
self.subscribers[subscriber.uuid] = subscriber
|
||||||
return subscriber
|
return subscriber
|
||||||
|
|
||||||
@state.LOCKER_SUBSCRIBER.lockthis
|
|
||||||
def remove_subscriber(self, uuid):
|
def remove_subscriber(self, uuid):
|
||||||
"""
|
"""
|
||||||
Removes a connected Plex Companion subscriber with machine identifier
|
Removes a connected Plex Companion subscriber with machine identifier
|
||||||
uuid from PKC notifications.
|
uuid from PKC notifications.
|
||||||
(Calls the cleanup() method of the subscriber)
|
(Calls the cleanup() method of the subscriber)
|
||||||
"""
|
"""
|
||||||
for subscriber in self.subscribers.values():
|
with state.LOCK_SUBSCRIBER:
|
||||||
if subscriber.uuid == uuid or subscriber.host == uuid:
|
for subscriber in self.subscribers.values():
|
||||||
subscriber.cleanup()
|
if subscriber.uuid == uuid or subscriber.host == uuid:
|
||||||
del self.subscribers[subscriber.uuid]
|
subscriber.cleanup()
|
||||||
|
del self.subscribers[subscriber.uuid]
|
||||||
|
|
||||||
def _cleanup(self):
|
def _cleanup(self):
|
||||||
for subscriber in self.subscribers.values():
|
for subscriber in self.subscribers.values():
|
||||||
|
|
|
@ -1,48 +1,17 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# THREAD SAFE
|
# THREAD SAFE
|
||||||
from threading import Lock, RLock
|
from threading import Lock, RLock
|
||||||
from functools import wraps
|
|
||||||
|
|
||||||
|
|
||||||
class LockFunction(object):
|
|
||||||
"""
|
|
||||||
Decorator for class methods and functions to lock them with lock.
|
|
||||||
|
|
||||||
Initialize this class first
|
|
||||||
lockfunction = LockFunction(lock), where lock is a threading.Lock() object
|
|
||||||
|
|
||||||
To then lock a function or method:
|
|
||||||
|
|
||||||
@lockfunction.lockthis
|
|
||||||
def some_function(args, kwargs)
|
|
||||||
"""
|
|
||||||
def __init__(self, lock):
|
|
||||||
self.lock = lock
|
|
||||||
|
|
||||||
def lockthis(self, func):
|
|
||||||
"""
|
|
||||||
Use this method to actually lock a function or method
|
|
||||||
"""
|
|
||||||
@wraps(func)
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
"""
|
|
||||||
Wrapper construct
|
|
||||||
"""
|
|
||||||
with self.lock:
|
|
||||||
result = func(*args, **kwargs)
|
|
||||||
return result
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
# LOCKS
|
# LOCKS
|
||||||
####################
|
####################
|
||||||
# Need to lock all methods and functions messing with subscribers
|
# Need to lock all methods and functions messing with Plex Companion subscribers
|
||||||
LOCK_SUBSCRIBER = RLock()
|
LOCK_SUBSCRIBER = RLock()
|
||||||
LOCKER_SUBSCRIBER = LockFunction(LOCK_SUBSCRIBER)
|
# Need to lock everything messing with Kodi/PKC playqueues
|
||||||
|
LOCK_PLAYQUEUES = RLock()
|
||||||
# Necessary to temporarily hold back librarysync/websocket listener when doing
|
# Necessary to temporarily hold back librarysync/websocket listener when doing
|
||||||
# a full sync
|
# a full sync
|
||||||
LOCK_PLAYLISTS = Lock()
|
LOCK_PLAYLISTS = Lock()
|
||||||
LOCKER_PLAYLISTS = LockFunction(LOCK_PLAYLISTS)
|
|
||||||
|
|
||||||
# Quit PKC
|
# Quit PKC
|
||||||
STOP_PKC = False
|
STOP_PKC = False
|
||||||
|
|
Loading…
Reference in a new issue