Big update

This commit is contained in:
croneter 2019-05-12 14:38:31 +02:00
parent a1f4960bca
commit ea4a062aac
5 changed files with 272 additions and 154 deletions

View file

@ -63,3 +63,6 @@ class PlayState(object):
self.context_menu_play = False self.context_menu_play = False
# Which Kodi player is/has been active? (either int 1, 2 or 3) # Which Kodi player is/has been active? (either int 1, 2 or 3)
self.active_players = set() self.active_players = set()
# Have we initiated playback via Plex Companion or Alexa - so from the
# Plex side of things?
self.initiated_by_plex = False

View file

@ -322,12 +322,10 @@ class KodiMonitor(xbmc.Monitor):
LOG.debug('Current Kodi playlist: %s', kodi_playlist) LOG.debug('Current Kodi playlist: %s', kodi_playlist)
kodi_item = PL.playlist_item_from_kodi(kodi_playlist[position]) kodi_item = PL.playlist_item_from_kodi(kodi_playlist[position])
if isinstance(self.playqueue.items[0], PL.PlaylistItemDummy): if isinstance(self.playqueue.items[0], PL.PlaylistItemDummy):
# Get rid of the very first element in the queue that Kodi marked # This dummy item will be deleted by webservice soon - it won't
# as unplayed (the one to init the queue) # play
LOG.debug('Deleting the very first playqueue item') LOG.debug('Dummy item detected')
js.playlist_remove(self.playqueue.playlistid, 0) position = 1
del self.playqueue.items[0]
position = 0
elif kodi_item != self.playqueue.items[position]: elif kodi_item != self.playqueue.items[position]:
LOG.debug('Different playqueue items: %s vs. %s ', LOG.debug('Different playqueue items: %s vs. %s ',
kodi_item, self.playqueue.items[position]) kodi_item, self.playqueue.items[position])
@ -487,9 +485,6 @@ def _record_playstate(status, ended):
playcount += 1 playcount += 1
time = 0 time = 0
with kodi_db.KodiVideoDB() as kodidb: with kodi_db.KodiVideoDB() as kodidb:
LOG.error('Setting file_id %s, time %s, totaltime %s, playcount %s, '
'last_played %s',
db_item['kodi_fileid'], time, totaltime, playcount, last_played)
kodidb.set_resume(db_item['kodi_fileid'], kodidb.set_resume(db_item['kodi_fileid'],
time, time,
totaltime, totaltime,

View file

@ -5,8 +5,7 @@ Collection of functions associated with Kodi and Plex playlists and playqueues
""" """
from __future__ import absolute_import, division, unicode_literals from __future__ import absolute_import, division, unicode_literals
from logging import getLogger from logging import getLogger
import threading
import xbmc
from .plex_api import API from .plex_api import API
from .plex_db import PlexDB from .plex_db import PlexDB
@ -139,20 +138,59 @@ class PlayQueue(object):
self.force_transcode = None self.force_transcode = None
LOG.debug('Playlist cleared: %s', self) LOG.debug('Playlist cleared: %s', self)
def init(self, plex_id, plex_type=None, position=None, synched=True, def play(self, plex_id, plex_type=None, startpos=None, position=None,
force_transcode=None): synched=True, force_transcode=None):
""" """
Initializes the playQueue with e.g. trailers and additional file parts Initializes the playQueue with e.g. trailers and additional file parts
Pass synched=False if you're sure that this item has not been synched Pass synched=False if you're sure that this item has not been synched
to Kodi to Kodi
Or resolves webservice paths to actual paths
"""
LOG.debug('Play called with plex_id %s, plex_type %s, position %s, '
'synched %s, force_transcode %s, startpos %s', plex_id,
plex_type, position, synched, force_transcode, startpos)
resolve = False
try:
if plex_id == self.items[startpos].plex_id:
resolve = True
except IndexError:
pass
if resolve:
LOG.info('Resolving playback')
self._resolve(plex_id, startpos)
else:
LOG.info('Initializing playback')
self.init(plex_id,
plex_type,
startpos,
position,
synched,
force_transcode)
def _resolve(self, plex_id, startpos):
"""
The Plex playqueue has already been initialized. We resolve the path
from original webservice http://127.0.0.1 to the "correct" Plex one
"""
self.index = startpos + 1
xml = PF.GetPlexMetadata(plex_id)
if xml in (None, 401):
raise PlaylistError('Could not get Plex metadata %s for %s',
plex_id, self.items[startpos])
api = API(xml[0])
resume = self._resume_playback(None, xml[0])
self._kodi_add_xml(xml[0], api, resume)
# Add additional file parts, if any exist
self._add_additional_parts(xml)
def init(self, plex_id, plex_type=None, startpos=None, position=None,
synched=True, force_transcode=None):
"""
Initializes the Plex and PKC playqueue for playback
""" """
LOG.error('Current Kodi playlist: %s',
js.playlist_get_items(self.playlistid))
LOG.debug('Initializing with plex_id %s, plex_type %s, position %s, '
'synched %s, force_transcode %s, index %s', plex_id,
plex_type, position, synched, force_transcode, self.index)
self.index = position self.index = position
if self.kodi_pl.size() != len(self.items): while len(self.items) < self.kodi_pl.size():
# The original item that Kodi put into the playlist, e.g. # The original item that Kodi put into the playlist, e.g.
# { # {
# u'title': u'', # u'title': u'',
@ -161,13 +199,10 @@ class PlayQueue(object):
# u'label': u'' # u'label': u''
# } # }
# We CANNOT delete that item right now - so let's add a dummy # We CANNOT delete that item right now - so let's add a dummy
# on the PKC side # on the PKC side to keep all indicees lined up.
LOG.debug('Detected Kodi playlist size %s to be off for PKC: %s', # The failing item will be deleted in webservice.py
self.kodi_pl.size(), len(self.items)) LOG.debug('Adding a dummy item to our playqueue')
while len(self.items) < self.kodi_pl.size(): self.items.insert(0, PlaylistItemDummy())
LOG.debug('Adding a dummy item to our playqueue')
playlistitem = PlaylistItemDummy()
self.items.insert(0, playlistitem)
self.force_transcode = force_transcode self.force_transcode = force_transcode
if synched: if synched:
with PlexDB(lock=False) as plexdb: with PlexDB(lock=False) as plexdb:
@ -316,7 +351,11 @@ class PlayQueue(object):
raise PlaylistError('Position %s too large for playlist length %s' raise PlaylistError('Position %s too large for playlist length %s'
% (pos, len(self.items))) % (pos, len(self.items)))
LOG.debug('Adding item to Kodi playlist at position %s: %s', pos, item) LOG.debug('Adding item to Kodi playlist at position %s: %s', pos, item)
if item.kodi_id is not None and item.kodi_type is not None: if listitem:
self.kodi_pl.add(url=listitem.getPath(),
listitem=listitem,
index=pos)
elif item.kodi_id is not None and item.kodi_type is not None:
# This method ensures we have full Kodi metadata, potentially # This method ensures we have full Kodi metadata, potentially
# with more artwork, for example, than Plex provides # with more artwork, for example, than Plex provides
if pos == len(self.items): if pos == len(self.items):
@ -330,24 +369,24 @@ class PlayQueue(object):
raise PlaylistError('Kodi did not add item to playlist: %s', raise PlaylistError('Kodi did not add item to playlist: %s',
answ) answ)
else: else:
if not listitem: if item.xml is None:
if item.xml is None: LOG.debug('Need to get metadata for item %s', item)
LOG.debug('Need to get metadata for item %s', item) item.xml = PF.GetPlexMetadata(item.plex_id)
item.xml = PF.GetPlexMetadata(item.plex_id) if item.xml in (None, 401):
if item.xml in (None, 401): raise PlaylistError('Could not get metadata for %s', item)
raise PlaylistError('Could not get metadata for %s', item) api = API(item.xml[0])
listitem = widgets.get_listitem(item.xml, resume=True) listitem = widgets.get_listitem(item.xml, resume=True)
url = 'http://127.0.0.1:%s/plex/play/file.strm' % v.WEBSERVICE_PORT url = 'http://127.0.0.1:%s/plex/play/file.strm' % v.WEBSERVICE_PORT
args = { args = {
'plex_id': self.plex_id, 'plex_id': item.plex_id,
'plex_type': self.plex_type 'plex_type': api.plex_type()
} }
if item.force_transcode: if item.force_transcode:
args['transcode'] = 'true' args['transcode'] = 'true'
url = utils.extend_url(url, args) url = utils.extend_url(url, args)
item.file = url item.file = url
listitem.setPath(url.encode('utf-8')) listitem.setPath(url.encode('utf-8'))
self.kodi_pl.add(url=listitem.getPath(), self.kodi_pl.add(url=url.encode('utf-8'),
listitem=listitem, listitem=listitem,
index=pos) index=pos)
@ -372,9 +411,6 @@ class PlayQueue(object):
except (TypeError, AttributeError, KeyError, IndexError): except (TypeError, AttributeError, KeyError, IndexError):
raise PlaylistError('Could not add item %s to playlist %s' raise PlaylistError('Could not add item %s to playlist %s'
% (item, self)) % (item, self))
if len(xml) != len(self.items) + 1:
raise PlaylistError('Could not add item %s to playlist %s - wrong'
' length received' % (item, self))
for actual_pos, xml_video_element in enumerate(xml): for actual_pos, xml_video_element in enumerate(xml):
api = API(xml_video_element) api = API(xml_video_element)
if api.plex_id() == item.plex_id: if api.plex_id() == item.plex_id:
@ -395,7 +431,7 @@ class PlayQueue(object):
LOG.debug('Removing position %s on the Kodi side for %s', pos, self) LOG.debug('Removing position %s on the Kodi side for %s', pos, self)
answ = js.playlist_remove(self.playlistid, pos) answ = js.playlist_remove(self.playlistid, pos)
if 'error' in answ: if 'error' in answ:
raise PlaylistError('Could not remove item: %s' % answ) raise PlaylistError('Could not remove item: %s' % answ['error'])
def plex_move_item(self, before, after): def plex_move_item(self, before, after):
""" """
@ -436,9 +472,71 @@ class PlayQueue(object):
self.items.insert(after, self.items.pop(before)) self.items.insert(after, self.items.pop(before))
LOG.debug('Done moving items for %s', self) LOG.debug('Done moving items for %s', self)
def start_playback(self, pos=0): def init_from_xml(self, xml, offset=None, start_plex_id=None, repeat=None,
LOG.info('Starting playback at %s for %s', pos, self) transient_token=None):
xbmc.Player().play(self.kodi_pl, startpos=pos, windowed=False) """
Play all items contained in the xml passed in. Called by Plex Companion.
Either supply the ratingKey of the starting Plex element. Or set
playqueue.selectedItemID
offset [float]: will seek to position offset after playback start
start_plex_id [int]: the plex_id of the element that should be
played
repeat [int]: 0: don't repear
1: repeat item
2: repeat everything
transient_token [unicode]: temporary token received from the PMS
Will stop current playback and start playback at the end
"""
LOG.debug("init_from_xml called with offset %s, start_plex_id %s",
offset, start_plex_id)
app.APP.player.stop()
self.clear()
self.update_details_from_xml(xml)
self.repeat = 0 if not repeat else repeat
self.plex_transient_token = transient_token
for pos, xml_video_element in enumerate(xml):
playlistitem = PlaylistItem(xml_video_element=xml_video_element)
self.kodi_add_item(playlistitem, pos)
self.items.append(playlistitem)
# Where do we start playback?
if start_plex_id is not None:
for startpos, item in enumerate(self.items):
if item.plex_id == start_plex_id:
break
else:
startpos = 0
else:
for startpos, item in enumerate(self.items):
if item.id == self.selectedItemID:
break
else:
startpos = 0
self.start_playback(pos=startpos, offset=offset)
def start_playback(self, pos=0, offset=0):
"""
Seek immediately after kicking off playback is not reliable.
Threaded, since we need to return BEFORE seeking
"""
LOG.info('Starting playback at %s offset %s for %s', pos, offset, self)
thread = threading.Thread(target=self._threaded_playback,
args=(self.kodi_pl, pos, offset))
thread.start()
@staticmethod
def _threaded_playback(kodi_playlist, pos, offset):
app.APP.player.play(kodi_playlist, startpos=pos, windowed=False)
if offset:
i = 0
while not app.APP.is_playing:
app.APP.monitor.waitForAbort(0.1)
i += 1
if i > 50:
LOG.warn('Could not seek to %s', offset)
return
js.seek_to(offset)
class PlaylistItem(object): class PlaylistItem(object):
@ -1069,7 +1167,7 @@ def move_playlist_item(playlist, before_pos, after_pos):
LOG.debug('Done moving for %s', playlist) LOG.debug('Done moving for %s', playlist)
def get_PMS_playlist(playlist, playlist_id=None): def get_PMS_playlist(playlist=None, playlist_id=None):
""" """
Fetches the PMS playlist/playqueue as an XML. Pass in playlist_id if we Fetches the PMS playlist/playqueue as an XML. Pass in playlist_id if we
need to fetch a new playlist need to fetch a new playlist
@ -1077,7 +1175,7 @@ def get_PMS_playlist(playlist, playlist_id=None):
Returns None if something went wrong Returns None if something went wrong
""" """
playlist_id = playlist_id if playlist_id else playlist.id playlist_id = playlist_id if playlist_id else playlist.id
if playlist.kind == 'playList': if playlist and playlist.kind == 'playList':
xml = DU().downloadUrl("{server}/playlists/%s/items" % playlist_id) xml = DU().downloadUrl("{server}/playlists/%s/items" % playlist_id)
else: else:
xml = DU().downloadUrl("{server}/playQueues/%s" % playlist_id) xml = DU().downloadUrl("{server}/playQueues/%s" % playlist_id)

View file

@ -15,7 +15,6 @@ from .plex_api import API
from . import utils from . import utils
from . import plex_functions as PF from . import plex_functions as PF
from . import playlist_func as PL from . import playlist_func as PL
from . import playback
from . import json_rpc as js from . import json_rpc as js
from . import playqueue as PQ from . import playqueue as PQ
from . import variables as v from . import variables as v
@ -40,6 +39,8 @@ def update_playqueue_from_PMS(playqueue,
repeat = 0, 1, 2 repeat = 0, 1, 2
offset = time offset in Plextime (milliseconds) offset = time offset in Plextime (milliseconds)
Will (re)start playback
""" """
LOG.info('New playqueue %s received from Plex companion with offset ' LOG.info('New playqueue %s received from Plex companion with offset '
'%s, repeat %s', playqueue_id, offset, repeat) '%s, repeat %s', playqueue_id, offset, repeat)
@ -47,21 +48,15 @@ def update_playqueue_from_PMS(playqueue,
if transient_token is None: if transient_token is None:
transient_token = playqueue.plex_transient_token transient_token = playqueue.plex_transient_token
with app.APP.lock_playqueues: with app.APP.lock_playqueues:
xml = PL.get_PMS_playlist(playqueue, playqueue_id) xml = PL.get_PMS_playlist(playlist_id=playqueue_id)
try: if xml is None:
xml.attrib
except AttributeError:
LOG.error('Could now download playqueue %s', playqueue_id) LOG.error('Could now download playqueue %s', playqueue_id)
return raise PL.PlaylistError()
playqueue.clear() app.PLAYSTATE.initiated_by_plex = True
try: playqueue.init_from_xml(xml,
PL.get_playlist_details_from_xml(playqueue, xml) offset=offset,
except PL.PlaylistError: repeat=0 if not repeat else int(repeat),
LOG.error('Could not get playqueue ID %s', playqueue_id) transient_token=transient_token)
return
playqueue.repeat = 0 if not repeat else int(repeat)
playqueue.plex_transient_token = transient_token
playback.play_xml(playqueue, xml, offset)
class PlexCompanion(backgroundthread.KillableThread): class PlexCompanion(backgroundthread.KillableThread):
@ -81,45 +76,48 @@ class PlexCompanion(backgroundthread.KillableThread):
@staticmethod @staticmethod
def _process_alexa(data): def _process_alexa(data):
app.PLAYSTATE.initiated_by_plex = True
xml = PF.GetPlexMetadata(data['key']) xml = PF.GetPlexMetadata(data['key'])
try: try:
xml[0].attrib xml[0].attrib
except (AttributeError, IndexError, TypeError): except (AttributeError, IndexError, TypeError):
LOG.error('Could not download Plex metadata for: %s', data) LOG.error('Could not download Plex metadata for: %s', data)
return raise PL.PlaylistError()
api = API(xml[0]) api = API(xml[0])
if api.plex_type() == v.PLEX_TYPE_ALBUM: if api.plex_type() == v.PLEX_TYPE_ALBUM:
LOG.debug('Plex music album detected') LOG.debug('Plex music album detected')
PQ.init_playqueue_from_plex_children( xml = PF.GetAllPlexChildren(api.plex_id())
api.plex_id(), try:
transient_token=data.get('token')) xml[0].attrib
except (TypeError, IndexError, AttributeError):
LOG.error('Could not download the album xml for %s', data)
raise PL.PlaylistError()
playqueue = PQ.get_playqueue_from_type('audio')
playqueue.init_from_xml(xml,
transient_token=data.get('token'))
elif data['containerKey'].startswith('/playQueues/'): elif data['containerKey'].startswith('/playQueues/'):
_, container_key, _ = PF.ParseContainerKey(data['containerKey']) _, container_key, _ = PF.ParseContainerKey(data['containerKey'])
xml = PF.DownloadChunks('{server}/playQueues/%s' % container_key) xml = PF.DownloadChunks('{server}/playQueues/%s' % container_key)
if xml is None: if xml is None:
# "Play error" LOG.error('Could not get playqueue for %s', data)
utils.dialog('notification', raise PL.PlaylistError()
utils.lang(29999),
utils.lang(30128),
icon='{error}')
return
playqueue = PQ.get_playqueue_from_type( playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type()]) v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type()])
playqueue.clear() if data.get('offset') not in ('0', None):
PL.get_playlist_details_from_xml(playqueue, xml)
playqueue.plex_transient_token = data.get('token')
if data.get('offset') != '0':
offset = float(data['offset']) / 1000.0 offset = float(data['offset']) / 1000.0
else: else:
offset = None offset = None
playback.play_xml(playqueue, xml, offset) playqueue.init_from_xml(xml,
offset=offset,
transient_token=data.get('token'))
else: else:
app.CONN.plex_transient_token = data.get('token') app.CONN.plex_transient_token = data.get('token')
if data.get('offset') != '0': if data.get('offset') not in (None, '0'):
app.PLAYSTATE.resume_playback = True app.PLAYSTATE.resume_playback = True
playback.playback_triage(api.plex_id(), path = ('http://127.0.0.1:%s/plex/play/file.strm?plex_id=%s'
api.plex_type(), % (v.WEBSERVICE_PORT, api.plex_id()))
resolve=False) path += '&plex_type=%s' % api.plex_type()
executebuiltin(('PlayMedia(%s)' % path).encode('utf-8'))
@staticmethod @staticmethod
def _process_node(data): def _process_node(data):
@ -150,7 +148,7 @@ class PlexCompanion(backgroundthread.KillableThread):
xml[0].attrib xml[0].attrib
except (AttributeError, IndexError, TypeError): except (AttributeError, IndexError, TypeError):
LOG.error('Could not download Plex metadata') LOG.error('Could not download Plex metadata')
return raise PL.PlaylistError()
api = API(xml[0]) api = API(xml[0])
playqueue = PQ.get_playqueue_from_type( playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type()]) v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type()])
@ -167,20 +165,23 @@ class PlexCompanion(backgroundthread.KillableThread):
""" """
playqueue = PQ.get_playqueue_from_type( playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[data['type']]) v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[data['type']])
pos = js.get_position(playqueue.playlistid) try:
if 'audioStreamID' in data: pos = js.get_position(playqueue.playlistid)
index = playqueue.items[pos].kodi_stream_index( if 'audioStreamID' in data:
data['audioStreamID'], 'audio')
app.APP.player.setAudioStream(index)
elif 'subtitleStreamID' in data:
if data['subtitleStreamID'] == '0':
app.APP.player.showSubtitles(False)
else:
index = playqueue.items[pos].kodi_stream_index( index = playqueue.items[pos].kodi_stream_index(
data['subtitleStreamID'], 'subtitle') data['audioStreamID'], 'audio')
app.APP.player.setSubtitleStream(index) app.APP.player.setAudioStream(index)
else: elif 'subtitleStreamID' in data:
LOG.error('Unknown setStreams command: %s', data) if data['subtitleStreamID'] == '0':
app.APP.player.showSubtitles(False)
else:
index = playqueue.items[pos].kodi_stream_index(
data['subtitleStreamID'], 'subtitle')
app.APP.player.setSubtitleStream(index)
else:
LOG.error('Unknown setStreams command: %s', data)
except KeyError:
LOG.warn('Could not process stream data: %s', data)
@staticmethod @staticmethod
def _process_refresh(data): def _process_refresh(data):
@ -220,23 +221,29 @@ class PlexCompanion(backgroundthread.KillableThread):
""" """
LOG.debug('Processing: %s', task) LOG.debug('Processing: %s', task)
data = task['data'] data = task['data']
if task['action'] == 'alexa': try:
with app.APP.lock_playqueues: if task['action'] == 'alexa':
self._process_alexa(data) with app.APP.lock_playqueues:
elif (task['action'] == 'playlist' and self._process_alexa(data)
data.get('address') == 'node.plexapp.com'): elif (task['action'] == 'playlist' and
self._process_node(data) data.get('address') == 'node.plexapp.com'):
elif task['action'] == 'playlist': self._process_node(data)
with app.APP.lock_playqueues: elif task['action'] == 'playlist':
self._process_playlist(data) with app.APP.lock_playqueues:
elif task['action'] == 'refreshPlayQueue': self._process_playlist(data)
with app.APP.lock_playqueues: elif task['action'] == 'refreshPlayQueue':
self._process_refresh(data) with app.APP.lock_playqueues:
elif task['action'] == 'setStreams': self._process_refresh(data)
try: elif task['action'] == 'setStreams':
self._process_streams(data) self._process_streams(data)
except KeyError: except PL.PlaylistError:
pass LOG.error('Could not process companion data: %s', data)
# "Play Error"
utils.dialog('notification',
utils.lang(29999),
utils.lang(30128),
icon='{error}')
app.PLAYSTATE.initiated_by_plex = False
def run(self): def run(self):
""" """

View file

@ -13,16 +13,16 @@ import Queue
import xbmc import xbmc
import xbmcvfs import xbmcvfs
from .plex_api import API
from .plex_db import PlexDB from .plex_db import PlexDB
from . import backgroundthread, utils, variables as v, app, playqueue as PQ from . import backgroundthread, utils, variables as v, app, playqueue as PQ
from . import playlist_func as PL, json_rpc as js from . import playlist_func as PL, json_rpc as js, plex_functions as PF
LOG = getLogger('PLEX.webservice') LOG = getLogger('PLEX.webservice')
class WebService(backgroundthread.KillableThread): class WebService(backgroundthread.KillableThread):
''' Run a webservice to trigger playback. ''' Run a webservice to trigger playback.
''' '''
def is_alive(self): def is_alive(self):
@ -48,7 +48,7 @@ class WebService(backgroundthread.KillableThread):
conn.request('QUIT', '/') conn.request('QUIT', '/')
conn.getresponse() conn.getresponse()
except Exception as error: except Exception as error:
xbmc.log('Plex.WebService abort error: %s' % error, xbmc.LOGWARNING) xbmc.log('PLEX.webservice abort error: %s' % error, xbmc.LOGWARNING)
def suspend(self): def suspend(self):
""" """
@ -124,7 +124,7 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
# Silence "[Errno 10054] An existing connection was forcibly # Silence "[Errno 10054] An existing connection was forcibly
# closed by the remote host" # closed by the remote host"
return return
xbmc.log('Plex.WebService handle error: %s' % error, xbmc.LOGWARNING) xbmc.log('PLEX.webservice handle error: %s' % error, xbmc.LOGWARNING)
def do_QUIT(self): def do_QUIT(self):
''' send 200 OK response, and set server.stop to True ''' send 200 OK response, and set server.stop to True
@ -144,7 +144,12 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
if '?' in path: if '?' in path:
path = path.split('?', 1)[1] path = path.split('?', 1)[1]
params = dict(utils.parse_qsl(path)) params = dict(utils.parse_qsl(path))
if 'plex_id' not in params:
LOG.error('No plex_id received for path %s', path)
return
if 'plex_type' in params and params['plex_type'].lower() == 'none':
del params['plex_type']
if 'plex_type' not in params: if 'plex_type' not in params:
LOG.debug('Need to look-up plex_type') LOG.debug('Need to look-up plex_type')
with PlexDB(lock=False) as plexdb: with PlexDB(lock=False) as plexdb:
@ -154,9 +159,20 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
else: else:
LOG.debug('No plex_type found, using Kodi player id') LOG.debug('No plex_type found, using Kodi player id')
players = js.get_players() players = js.get_players()
params['plex_type'] = v.PLEX_TYPE_CLIP if 'video' in players \ if players:
else v.PLEX_TYPE_SONG params['plex_type'] = v.PLEX_TYPE_CLIP if 'video' in players \
else v.PLEX_TYPE_SONG
LOG.debug('Using the following plex_type: %s',
params['plex_type'])
else:
xml = PF.GetPlexMetadata(params['plex_id'])
if xml in (None, 401):
LOG.error('Could not get metadata for %s', params)
return
api = API(xml[0])
params['plex_type'] = api.plex_type()
LOG.debug('Got metadata, using plex_type %s',
params['plex_type'])
return params return params
def do_HEAD(self): def do_HEAD(self):
@ -172,7 +188,7 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def handle_request(self, headers_only=False): def handle_request(self, headers_only=False):
'''Send headers and reponse '''Send headers and reponse
''' '''
xbmc.log('Plex.WebService handle_request called. headers %s, path: %s' xbmc.log('PLEX.webservice handle_request called. headers %s, path: %s'
% (headers_only, self.path), xbmc.LOGDEBUG) % (headers_only, self.path), xbmc.LOGDEBUG)
try: try:
if b'extrafanart' in self.path or b'extrathumbs' in self.path: if b'extrafanart' in self.path or b'extrathumbs' in self.path:
@ -311,11 +327,13 @@ class QueuePlay(backgroundthread.KillableThread):
self.synched = not params['synched'].lower() == 'false' self.synched = not params['synched'].lower() == 'false'
def _get_playqueue(self): def _get_playqueue(self):
if (self.plex_type in v.PLEX_VIDEOTYPES and playqueue = PQ.get_playqueue_from_type(v.KODI_TYPE_VIDEO)
xbmc.getCondVisibility('Window.IsVisible(Home.xml)')): if ((self.plex_type in v.PLEX_VIDEOTYPES and
not app.PLAYSTATE.initiated_by_plex and
xbmc.getCondVisibility('Window.IsVisible(Home.xml)'))):
# Video launched from a widget - which starts a Kodi AUDIO playlist # Video launched from a widget - which starts a Kodi AUDIO playlist
# We will empty everything and start with a fresh VIDEO playlist # We will empty everything and start with a fresh VIDEO playlist
LOG.debug('Widget video playback detected; relaunching') LOG.debug('Widget video playback detected')
video_widget_playback = True video_widget_playback = True
# Release default.py # Release default.py
utils.window('plex.playlist.ready', value='true') utils.window('plex.playlist.ready', value='true')
@ -335,14 +353,9 @@ class QueuePlay(backgroundthread.KillableThread):
else: else:
LOG.debug('Audio playback detected') LOG.debug('Audio playback detected')
playqueue = PQ.get_playqueue_from_type(v.KODI_TYPE_AUDIO) playqueue = PQ.get_playqueue_from_type(v.KODI_TYPE_AUDIO)
playqueue.clear(kodi=False)
return playqueue, video_widget_playback return playqueue, video_widget_playback
def run(self): def run(self):
"""
We cannot use js.get_players() to reliably get the active player
Use Kodimonitor's OnNotification and OnAdd
"""
LOG.debug('##===---- Starting QueuePlay ----===##') LOG.debug('##===---- Starting QueuePlay ----===##')
abort = False abort = False
play_folder = False play_folder = False
@ -358,6 +371,8 @@ class QueuePlay(backgroundthread.KillableThread):
# Position to add next element to queue - we're doing this at the end # Position to add next element to queue - we're doing this at the end
# of our current playqueue # of our current playqueue
position = playqueue.kodi_pl.size() position = playqueue.kodi_pl.size()
# Set to start_position + 1 because first item will fail
utils.window('plex.playlist.start', str(start_position + 1))
LOG.debug('start_position %s, position %s for current playqueue: %s', LOG.debug('start_position %s, position %s for current playqueue: %s',
start_position, position, playqueue) start_position, position, playqueue)
while True: while True:
@ -370,7 +385,7 @@ class QueuePlay(backgroundthread.KillableThread):
LOG.debug('Wrapping up') LOG.debug('Wrapping up')
if xbmc.getCondVisibility('VideoPlayer.Content(livetv)'): if xbmc.getCondVisibility('VideoPlayer.Content(livetv)'):
# avoid issues with ongoing Live TV playback # avoid issues with ongoing Live TV playback
xbmc.Player().stop() app.APP.player.stop()
count = 50 count = 50
while not utils.window('plex.playlist.ready'): while not utils.window('plex.playlist.ready'):
xbmc.sleep(50) xbmc.sleep(50)
@ -392,48 +407,47 @@ class QueuePlay(backgroundthread.KillableThread):
LOG.info('Start normal playback') LOG.info('Start normal playback')
# Release default.py # Release default.py
utils.window('plex.playlist.play', value='true') utils.window('plex.playlist.play', value='true')
# Remove the playlist element we just added with the
# right path
xbmc.sleep(1000)
playqueue.kodi_remove_item(start_position)
del playqueue.items[start_position]
LOG.debug('Done wrapping up') LOG.debug('Done wrapping up')
break break
self.load_params(params) self.load_params(params)
if play_folder: if play_folder:
# position = play.play_folder(position) playlistitem = PL.PlaylistItem(plex_id=self.plex_id,
item = PL.PlaylistItem(plex_id=self.plex_id, plex_type=self.plex_type,
plex_type=self.plex_type, kodi_id=self.kodi_id,
kodi_id=self.kodi_id, kodi_type=self.kodi_type)
kodi_type=self.kodi_type) playlistitem.force_transcode = self.force_transcode
item.force_transcode = self.force_transcode playqueue.add_item(playlistitem, position)
playqueue.add_item(item, position)
position += 1 position += 1
else: else:
if self.server.pending.count(params['plex_id']) != len(self.server.pending): if self.server.pending.count(params['plex_id']) != len(self.server.pending):
# E.g. when selecting "play" for an entire video genre
LOG.debug('Folder playback detected') LOG.debug('Folder playback detected')
play_folder = True play_folder = True
# Set to start_position + 1 because first item will fail xbmc.executebuiltin('Activateutils.window(busydialognocancel)')
utils.window('plex.playlist.start', str(start_position + 1)) playqueue.play(self.plex_id,
playqueue.init(self.plex_id,
plex_type=self.plex_type, plex_type=self.plex_type,
startpos=start_position,
position=position, position=position,
synched=self.synched, synched=self.synched,
force_transcode=self.force_transcode) force_transcode=self.force_transcode)
# Do NOT start playback here - because Kodi already started # Do NOT start playback here - because Kodi already started
# it! # it!
# playqueue.start_playback(position)
position = playqueue.index position = playqueue.index
if play_folder:
xbmc.executebuiltin('Activateutils.window(busydialognocancel)')
except PL.PlaylistError as error:
abort = True
LOG.warn('Not playing due to the following: %s', error)
except Exception: except Exception:
abort = True abort = True
utils.ERROR() utils.ERROR(notify=True)
try: try:
self.server.queue.task_done() self.server.queue.task_done()
except ValueError: except ValueError:
# "task_done() called too many times" # "task_done() called too many times" when aborting
pass pass
if abort: if abort:
xbmc.Player().stop() app.APP.player.stop()
playqueue.clear() playqueue.clear()
self.server.queue.queue.clear() self.server.queue.queue.clear()
if play_folder: if play_folder:
@ -444,6 +458,7 @@ class QueuePlay(backgroundthread.KillableThread):
utils.window('plex.playlist.ready', clear=True) utils.window('plex.playlist.ready', clear=True)
utils.window('plex.playlist.start', clear=True) utils.window('plex.playlist.start', clear=True)
app.PLAYSTATE.initiated_by_plex = False
self.server.threads.remove(self) self.server.threads.remove(self)
self.server.pending = [] self.server.pending = []
LOG.debug('##===---- QueuePlay Stopped ----===##') LOG.debug('##===---- QueuePlay Stopped ----===##')