Big update
This commit is contained in:
parent
a1f4960bca
commit
ea4a062aac
5 changed files with 272 additions and 154 deletions
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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 ----===##')
|
||||||
|
|
Loading…
Reference in a new issue