Switch to stream playback, part I
This commit is contained in:
parent
edb9d6e2b0
commit
9b4584e7df
7 changed files with 612 additions and 4 deletions
|
@ -217,7 +217,8 @@ def show_listing(xml, plex_type=None, section_id=None, synched=True, key=None,
|
|||
# Need to chain keys for navigation
|
||||
widgets.KEY = key
|
||||
# Process all items to show
|
||||
widgets.attach_kodi_ids(xml)
|
||||
if synched:
|
||||
widgets.attach_kodi_ids(xml)
|
||||
all_items = widgets.process_method_on_list(widgets.generate_item, xml)
|
||||
all_items = widgets.process_method_on_list(widgets.prepare_listitem,
|
||||
all_items)
|
||||
|
|
255
resources/lib/playstrm.py
Normal file
255
resources/lib/playstrm.py
Normal file
|
@ -0,0 +1,255 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, division, unicode_literals
|
||||
from logging import getLogger
|
||||
import urllib
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
|
||||
from .plex_api import API
|
||||
from . import plex_function as PF, utils, json_rpc, variables as v, \
|
||||
widgets
|
||||
|
||||
|
||||
LOG = getLogger('PLEX.playstrm')
|
||||
|
||||
|
||||
class PlayStrmException(Exception):
|
||||
"""
|
||||
Any Exception associated with playstrm
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class PlayStrm(object):
|
||||
'''
|
||||
Workflow: Strm that calls our webservice in database. When played, the
|
||||
webserivce returns a dummy file to play. Meanwhile, PlayStrm adds the real
|
||||
listitems for items to play to the playlist.
|
||||
'''
|
||||
def __init__(self, params, server_id=None):
|
||||
LOG.debug('Starting PlayStrm with server_id %s, params: %s',
|
||||
server_id, params)
|
||||
self.xml = None
|
||||
self.api = None
|
||||
self.start_index = None
|
||||
self.index = None
|
||||
self.server_id = server_id
|
||||
self.plex_id = utils.cast(int, params['plex_id'])
|
||||
self.plex_type = params.get('plex_type')
|
||||
if params.get('synched') and params['synched'].lower() == 'false':
|
||||
self.synched = False
|
||||
else:
|
||||
self.synched = True
|
||||
self._get_xml()
|
||||
self.name = self.api.title()
|
||||
self.kodi_id = utils.cast(int, params.get('kodi_id'))
|
||||
self.kodi_type = params.get('kodi_type')
|
||||
if ((self.kodi_id is None or self.kodi_type is None) and
|
||||
self.xml[0].get('pkc_db_item')):
|
||||
self.kodi_id = self.xml[0].get('pkc_db_item')['kodi_id']
|
||||
self.kodi_type = self.xml[0].get('pkc_db_item')['kodi_type']
|
||||
self.transcode = params.get('transcode')
|
||||
if self.transcode is None:
|
||||
self.transcode = utils.settings('playFromTranscode.bool') if utils.settings('playFromStream.bool') else None
|
||||
if utils.window('plex.playlist.audio.bool'):
|
||||
LOG.info('Audio playlist detected')
|
||||
self.kodi_playlist = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
|
||||
else:
|
||||
self.kodi_playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||
|
||||
def __repr__(self):
|
||||
return ("{{"
|
||||
"'name': '{self.name}', "
|
||||
"'plex_id': {self.plex_id}, "
|
||||
"'plex_type': '{self.plex_type}', "
|
||||
"'kodi_id': {self.kodi_id}, "
|
||||
"'kodi_type': '{self.kodi_type}', "
|
||||
"'server_id': '{self.server_id}', "
|
||||
"'transcode': {self.transcode}, "
|
||||
"'start_index': {self.start_index}, "
|
||||
"'index': {self.index}"
|
||||
"}}").format(self=self).encode('utf-8')
|
||||
__str__ = __repr__
|
||||
|
||||
def add_to_playlist(self, kodi_id, kodi_type, index=None, playlistid=None):
|
||||
playlistid = playlistid or self.kodi_playlist.getPlayListId()
|
||||
LOG.debug('Adding kodi_id %s, kodi_type %s to playlist %s at index %s',
|
||||
kodi_id, kodi_type, playlistid, index)
|
||||
if index is None:
|
||||
json_rpc.playlist_add(playlistid, {'%sid' % kodi_type: kodi_id})
|
||||
else:
|
||||
json_rpc.playlist_insert({'playlistid': playlistid,
|
||||
'position': index,
|
||||
'item': {'%sid' % kodi_type: kodi_id}})
|
||||
|
||||
def remove_from_playlist(self, index):
|
||||
LOG.debug('Removing playlist item number %s from %s', index, self)
|
||||
json_rpc.playlist_remove(self.kodi_playlist.getPlayListId(),
|
||||
index)
|
||||
|
||||
def _get_xml(self):
|
||||
self.xml = PF.GetPlexMetadata(self.plex_id)
|
||||
if self.xml in (None, 401):
|
||||
raise PlayStrmException('No xml received from the PMS')
|
||||
if self.synched:
|
||||
# Adds a new key 'pkc_db_item' to self.xml[0].attrib
|
||||
widgets.attach_kodi_ids(self.xml)
|
||||
else:
|
||||
self.xml[0].set('pkc_db_item', None)
|
||||
self.api = API(self.xml[0])
|
||||
|
||||
def start_playback(self, index=0):
|
||||
LOG.debug('Starting playback at %s', index)
|
||||
xbmc.Player().play(self.kodi_playlist, startpos=index, windowed=False)
|
||||
|
||||
def play(self, start_position=None, delayed=True):
|
||||
'''
|
||||
Create and add listitems to the Kodi playlist.
|
||||
'''
|
||||
if start_position is not None:
|
||||
self.start_index = start_position
|
||||
else:
|
||||
self.start_index = max(self.kodi_playlist.getposition(), 0)
|
||||
self.index = self.start_index
|
||||
listitem = xbmcgui.ListItem()
|
||||
self._set_playlist(listitem)
|
||||
LOG.info('Initiating play for %s', self)
|
||||
if not delayed:
|
||||
self.start_playback(self.start_index)
|
||||
return self.start_index
|
||||
|
||||
def play_folder(self, position=None):
|
||||
'''
|
||||
When an entire queue is requested, If requested from Kodi, kodi_type is
|
||||
provided, add as Kodi would, otherwise queue playlist items using strm
|
||||
links to setup playback later.
|
||||
'''
|
||||
self.start_index = position or max(self.kodi_playlist.size(), 0)
|
||||
self.index = self.start_index + 1
|
||||
LOG.info('Play folder plex_id %s, index: %s', self.plex_id, self.index)
|
||||
if self.kodi_id and self.kodi_type:
|
||||
self.add_to_playlist(self.kodi_id, self.kodi_type, self.index)
|
||||
else:
|
||||
listitem = widgets.get_listitem(self.xml[0])
|
||||
url = 'http://127.0.0.1:%s/plex/play/file.strm' % v.WEBSERVICE_PORT
|
||||
args = {
|
||||
'mode': 'play',
|
||||
'plex_id': self.plex_id,
|
||||
'plex_type': self.api.plex_type()
|
||||
}
|
||||
if self.kodi_id:
|
||||
args['kodi_id'] = self.kodi_id
|
||||
if self.kodi_type:
|
||||
args['kodi_type'] = self.kodi_type
|
||||
if self.server_id:
|
||||
args['server_id'] = self.server_id
|
||||
if self.transcode:
|
||||
args['transcode'] = True
|
||||
url = '%s?%s' % (url, urllib.urlencode(args))
|
||||
listitem.setPath(url)
|
||||
self.kodi_playlist.add(url=url,
|
||||
listitem=listitem,
|
||||
index=self.index)
|
||||
return self.index
|
||||
|
||||
def _set_playlist(self, listitem):
|
||||
'''
|
||||
Verify seektime, set intros, set main item and set additional parts.
|
||||
Detect the seektime for video type content. Verify the default video
|
||||
action set in Kodi for accurate resume behavior.
|
||||
'''
|
||||
seektime = self._resume()
|
||||
if (not seektime and self.plex_type == v.PLEX_TYPE_MOVIE and
|
||||
utils.settings('enableCinema') == 'true'):
|
||||
self._set_intros()
|
||||
|
||||
play = playutils.PlayUtilsStrm(self.xml, self.transcode, self.server_id, self.info['Server'])
|
||||
source = play.select_source(play.get_sources())
|
||||
|
||||
if not source:
|
||||
raise PlayStrmException('Playback selection cancelled')
|
||||
|
||||
play.set_external_subs(source, listitem)
|
||||
self.set_listitem(self.xml, listitem, self.kodi_id, seektime)
|
||||
listitem.setPath(self.xml['PlaybackInfo']['Path'])
|
||||
playutils.set_properties(self.xml, self.xml['PlaybackInfo']['Method'], self.server_id)
|
||||
|
||||
self.kodi_playlist.add(url=self.xml['PlaybackInfo']['Path'], listitem=listitem, index=self.index)
|
||||
self.index += 1
|
||||
|
||||
if self.xml.get('PartCount'):
|
||||
self._set_additional_parts()
|
||||
|
||||
def _resume(self):
|
||||
'''
|
||||
Resume item if available. Returns bool or raise an PlayStrmException if
|
||||
resume was cancelled by user.
|
||||
'''
|
||||
seektime = utils.window('plex.resume')
|
||||
utils.window('plex.resume', clear=True)
|
||||
seektime = seektime == 'true' if seektime else None
|
||||
auto_play = utils.window('plex.autoplay.bool')
|
||||
if auto_play:
|
||||
seektime = False
|
||||
LOG.info('Skip resume for autoplay')
|
||||
elif seektime is None:
|
||||
resume = self.api.resume_point()
|
||||
if resume:
|
||||
seektime = resume_dialog(resume)
|
||||
LOG.info('Resume: %s', seektime)
|
||||
if seektime is None:
|
||||
raise PlayStrmException('User backed out of resume dialog.')
|
||||
# Todo: Probably need to have a look here
|
||||
utils.window('plex.autoplay.bool', value='true')
|
||||
return seektime
|
||||
|
||||
def _set_intros(self):
|
||||
'''
|
||||
if we have any play them when the movie/show is not being resumed.
|
||||
'''
|
||||
if self.info['Intros']['Items']:
|
||||
enabled = True
|
||||
|
||||
if utils.settings('askCinema') == 'true':
|
||||
|
||||
resp = dialog('yesno', heading='{emby}', line1=_(33016))
|
||||
if not resp:
|
||||
|
||||
enabled = False
|
||||
LOG.info('Skip trailers.')
|
||||
|
||||
if enabled:
|
||||
for intro in self.info['Intros']['Items']:
|
||||
|
||||
listitem = xbmcgui.ListItem()
|
||||
LOG.info('[ intro/%s/%s ] %s', intro['plex_id'], self.index, intro['Name'])
|
||||
|
||||
play = playutils.PlayUtilsStrm(intro, False, self.server_id, self.info['Server'])
|
||||
source = play.select_source(play.get_sources())
|
||||
self.set_listitem(intro, listitem, intro=True)
|
||||
listitem.setPath(intro['PlaybackInfo']['Path'])
|
||||
playutils.set_properties(intro, intro['PlaybackInfo']['Method'], self.server_id)
|
||||
|
||||
self.kodi_playlist.add(url=intro['PlaybackInfo']['Path'], listitem=listitem, index=self.index)
|
||||
self.index += 1
|
||||
|
||||
utils.window('plex.skip.%s' % intro['plex_id'], value='true')
|
||||
|
||||
def _set_additional_parts(self):
|
||||
''' Create listitems and add them to the stack of playlist.
|
||||
'''
|
||||
for part in self.info['AdditionalParts']['Items']:
|
||||
|
||||
listitem = xbmcgui.ListItem()
|
||||
LOG.info('[ part/%s/%s ] %s', part['plex_id'], self.index, part['Name'])
|
||||
|
||||
play = playutils.PlayUtilsStrm(part, self.transcode, self.server_id, self.info['Server'])
|
||||
source = play.select_source(play.get_sources())
|
||||
play.set_external_subs(source, listitem)
|
||||
self.set_listitem(part, listitem)
|
||||
listitem.setPath(part['PlaybackInfo']['Path'])
|
||||
playutils.set_properties(part, part['PlaybackInfo']['Method'], self.server_id)
|
||||
|
||||
self.kodi_playlist.add(url=part['PlaybackInfo']['Path'], listitem=listitem, index=self.index)
|
||||
self.index += 1
|
|
@ -12,3 +12,18 @@ from .sections import Sections
|
|||
|
||||
class PlexDB(PlexDBBase, TVShows, Movies, Music, Playlists, Sections):
|
||||
pass
|
||||
|
||||
|
||||
def kodi_from_plex(plex_id, plex_type=None):
|
||||
"""
|
||||
Returns the tuple (kodi_id, kodi_type) for plex_id. Faster, if plex_type
|
||||
is provided
|
||||
|
||||
Returns (None, None) if unsuccessful
|
||||
"""
|
||||
with PlexDB(lock=False) as plexdb:
|
||||
db_item = plexdb.item_by_id(plex_id, plex_type)
|
||||
if db_item:
|
||||
return (db_item['kodi_id'], db_item['kodi_type'])
|
||||
else:
|
||||
return None, None
|
||||
|
|
|
@ -10,6 +10,7 @@ from . import initialsetup
|
|||
from . import kodimonitor
|
||||
from . import sync, library_sync
|
||||
from . import websocket_client
|
||||
from . import webservice
|
||||
from . import plex_companion
|
||||
from . import plex_functions as PF, playqueue as PQ
|
||||
from . import playback_starter
|
||||
|
@ -433,6 +434,7 @@ class Service(object):
|
|||
self.setup.setup()
|
||||
|
||||
# Initialize important threads
|
||||
self.webservice = webservice.WebService()
|
||||
self.ws = websocket_client.PMS_Websocket()
|
||||
self.alexa = websocket_client.Alexa_Websocket()
|
||||
self.sync = sync.Sync()
|
||||
|
@ -494,6 +496,12 @@ class Service(object):
|
|||
xbmc.sleep(100)
|
||||
continue
|
||||
|
||||
if self.webservice is not None and not self.webservice.is_alive():
|
||||
LOG.info('Restarting webservice')
|
||||
self.webservice.abort()
|
||||
self.webservice = webservice.WebService()
|
||||
self.webservice.start()
|
||||
|
||||
# Before proceeding, need to make sure:
|
||||
# 1. Server is online
|
||||
# 2. User is set
|
||||
|
@ -523,6 +531,7 @@ class Service(object):
|
|||
continue
|
||||
elif not self.startup_completed:
|
||||
self.startup_completed = True
|
||||
self.webservice.start()
|
||||
self.ws.start()
|
||||
self.sync.start()
|
||||
self.plexcompanion.start()
|
||||
|
|
|
@ -92,6 +92,9 @@ DEVICENAME = DEVICENAME.replace(' ', "")
|
|||
|
||||
COMPANION_PORT = int(_ADDON.getSetting('companionPort'))
|
||||
|
||||
# Port for the PKC webservice
|
||||
WEBSERVICE_PORT = 57578
|
||||
|
||||
# Unique ID for this Plex client; also see clientinfo.py
|
||||
PKC_MACHINE_IDENTIFIER = None
|
||||
|
||||
|
|
319
resources/lib/webservice.py
Normal file
319
resources/lib/webservice.py
Normal file
|
@ -0,0 +1,319 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
PKC-dedicated webserver. Listens to Kodi starting playback; will then hand-over
|
||||
playback to plugin://video.plexkodiconnect
|
||||
'''
|
||||
from __future__ import absolute_import, division, unicode_literals
|
||||
from logging import getLogger
|
||||
import BaseHTTPServer
|
||||
import httplib
|
||||
import urlparse
|
||||
import socket
|
||||
import Queue
|
||||
|
||||
import xbmc
|
||||
import xbmcvfs
|
||||
|
||||
from . import backgroundthread, utils, variables as v, app
|
||||
from .playstrm import PlayStrm
|
||||
|
||||
|
||||
LOG = getLogger('PLEX.webservice')
|
||||
|
||||
|
||||
class WebService(backgroundthread.KillableThread):
|
||||
|
||||
''' Run a webservice to trigger playback.
|
||||
'''
|
||||
def is_alive(self):
|
||||
''' Called to see if the webservice is still responding.
|
||||
'''
|
||||
alive = True
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect(('127.0.0.1', v.WEBSERVICE_PORT))
|
||||
s.sendall('')
|
||||
except Exception as error:
|
||||
LOG.error(error)
|
||||
if 'Errno 61' in str(error):
|
||||
alive = False
|
||||
s.close()
|
||||
return alive
|
||||
|
||||
def abort(self):
|
||||
''' Called when the thread needs to stop
|
||||
'''
|
||||
try:
|
||||
conn = httplib.HTTPConnection('127.0.0.1:%d' % v.WEBSERVICE_PORT)
|
||||
conn.request('QUIT', '/')
|
||||
conn.getresponse()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
''' Called to start the webservice.
|
||||
'''
|
||||
LOG.info('----===## Starting Webserver on port %s ##===----',
|
||||
v.WEBSERVICE_PORT)
|
||||
app.APP.register_thread(self)
|
||||
try:
|
||||
server = HttpServer(('127.0.0.1', v.WEBSERVICE_PORT),
|
||||
RequestHandler)
|
||||
server.serve_forever()
|
||||
except Exception as error:
|
||||
if '10053' not in error: # ignore host diconnected errors
|
||||
utils.ERROR()
|
||||
finally:
|
||||
app.APP.deregister_thread(self)
|
||||
LOG.info('##===---- Webserver stopped ----===##')
|
||||
|
||||
|
||||
class HttpServer(BaseHTTPServer.HTTPServer):
|
||||
''' Http server that reacts to self.stop flag.
|
||||
'''
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.stop = False
|
||||
self.pending = []
|
||||
self.threads = []
|
||||
self.queue = Queue.Queue()
|
||||
super(HttpServer, self).__init__(*args, **kwargs)
|
||||
|
||||
def serve_forever(self):
|
||||
|
||||
''' Handle one request at a time until stopped.
|
||||
'''
|
||||
while not self.stop:
|
||||
self.handle_request()
|
||||
|
||||
|
||||
class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
''' Http request handler. Do not use LOG here,
|
||||
it will hang requests in Kodi > show information dialog.
|
||||
'''
|
||||
timeout = 0.5
|
||||
|
||||
def log_message(self, format, *args):
|
||||
''' Mute the webservice requests.
|
||||
'''
|
||||
pass
|
||||
|
||||
def handle(self):
|
||||
''' To quiet socket errors with 404.
|
||||
'''
|
||||
try:
|
||||
BaseHTTPServer.BaseHTTPRequestHandler.handle(self)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def do_QUIT(self):
|
||||
''' send 200 OK response, and set server.stop to True
|
||||
'''
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.server.stop = True
|
||||
|
||||
def get_params(self):
|
||||
''' Get the params as a dict
|
||||
'''
|
||||
try:
|
||||
path = self.path[1:].decode('utf-8')
|
||||
except IndexError:
|
||||
params = {}
|
||||
if '?' in path:
|
||||
path = path.split('?', 1)[1]
|
||||
params = dict(urlparse.parse_qsl(path))
|
||||
|
||||
if params.get('transcode'):
|
||||
params['transcode'] = params['transcode'].lower() == 'true'
|
||||
if params.get('server') and params['server'].lower() == 'none':
|
||||
params['server'] = None
|
||||
|
||||
return params
|
||||
|
||||
def do_HEAD(self):
|
||||
''' Called on HEAD requests
|
||||
'''
|
||||
self.handle_request(True)
|
||||
|
||||
def do_GET(self):
|
||||
''' Called on GET requests
|
||||
'''
|
||||
self.handle_request()
|
||||
|
||||
def handle_request(self, headers_only=False):
|
||||
'''Send headers and reponse
|
||||
'''
|
||||
try:
|
||||
if b'extrafanart' in self.path or b'extrathumbs' in self.path:
|
||||
raise Exception('unsupported artwork request')
|
||||
|
||||
if headers_only:
|
||||
self.send_response(200)
|
||||
self.send_header(b'Content-type', b'text/html')
|
||||
self.end_headers()
|
||||
|
||||
elif b'file.strm' not in self.path:
|
||||
self.images()
|
||||
elif b'file.strm' in self.path:
|
||||
self.strm()
|
||||
else:
|
||||
xbmc.log(str(self.path), xbmc.LOGWARNING)
|
||||
|
||||
except Exception as error:
|
||||
self.send_error(500,
|
||||
b'PLEX.webservice: Exception occurred: %s' % error)
|
||||
xbmc.log('<[ webservice/%s/%s ]' % (str(id(self)), int(not headers_only)), xbmc.LOGWARNING)
|
||||
|
||||
def strm(self):
|
||||
''' Return a dummy video and and queue real items.
|
||||
'''
|
||||
self.send_response(200)
|
||||
self.send_header(b'Content-type', b'text/html')
|
||||
self.end_headers()
|
||||
|
||||
params = self.get_params()
|
||||
|
||||
if b'kodi/movies' in self.path:
|
||||
params['kodi_type'] = v.KODI_TYPE_MOVIE
|
||||
elif b'kodi/tvshows' in self.path:
|
||||
params['kodi_type'] = v.KODI_TYPE_EPISODE
|
||||
# elif 'kodi/musicvideos' in self.path:
|
||||
# params['MediaType'] = 'musicvideo'
|
||||
|
||||
if utils.settings('pluginSingle.bool'):
|
||||
path = 'plugin://plugin.video.plexkodiconnect?mode=playsingle&plex_id=%s' % params['plex_id']
|
||||
if params.get('server'):
|
||||
path += '&server=%s' % params['server']
|
||||
if params.get('transcode'):
|
||||
path += '&transcode=true'
|
||||
if params.get('kodi_id'):
|
||||
path += '&kodi_id=%s' % params['kodi_id']
|
||||
if params.get('Name'):
|
||||
path += '&filename=%s' % params['Name']
|
||||
self.wfile.write(bytes(path))
|
||||
return
|
||||
|
||||
path = 'plugin://plugin.video.plexkodiconnect?mode=playstrm&plex_id=%s' % params['plex_id']
|
||||
self.wfile.write(bytes(path))
|
||||
if params['plex_id'] not in self.server.pending:
|
||||
xbmc.log('PLEX.webserver: %s: path: %s params: %s'
|
||||
% (str(id(self)), str(self.path), str(params)),
|
||||
xbmc.LOGWARNING)
|
||||
|
||||
self.server.pending.append(params['plex_id'])
|
||||
self.server.queue.put(params)
|
||||
if not len(self.server.threads):
|
||||
queue = QueuePlay(self.server)
|
||||
queue.start()
|
||||
self.server.threads.append(queue)
|
||||
|
||||
def images(self):
|
||||
''' Return a dummy image for unwanted images requests over the webservice.
|
||||
Required to prevent freezing of widget playback if the file url has no
|
||||
local textures cached yet.
|
||||
'''
|
||||
image = xbmc.translatePath(
|
||||
'special://home/addons/plugin.video.plexkodiconnect/icon.png').decode('utf-8')
|
||||
self.send_response(200)
|
||||
self.send_header(b'Content-type', b'image/png')
|
||||
modified = xbmcvfs.Stat(image).st_mtime()
|
||||
self.send_header(b'Last-Modified', b'%s' % modified)
|
||||
image = xbmcvfs.File(image)
|
||||
size = image.size()
|
||||
self.send_header(b'Content-Length', str(size))
|
||||
self.end_headers()
|
||||
self.wfile.write(image.readBytes())
|
||||
image.close()
|
||||
|
||||
|
||||
class QueuePlay(backgroundthread.KillableThread):
|
||||
''' Workflow for new playback:
|
||||
|
||||
Queue up strm playback that was called in the webservice. Called
|
||||
playstrm in default.py which will wait for our signal here. Downloads
|
||||
plex information. Add content to the playlist after the strm file that
|
||||
initiated playback from db. Start playback by telling playstrm waiting.
|
||||
It will fail playback of the current strm and move to the next entry for
|
||||
us. If play folder, playback starts here.
|
||||
|
||||
Required delay for widgets, custom skin containers and non library
|
||||
windows. Otherwise Kodi will freeze if no artwork textures are cached
|
||||
yet in Textures13.db Will be skipped if the player already has media and
|
||||
is playing.
|
||||
|
||||
Why do all this instead of using plugin? Strms behaves better than
|
||||
plugin in database. Allows to load chapter images with direct play.
|
||||
Allows to have proper artwork for intros. Faster than resolving using
|
||||
plugin, especially on low powered devices. Cons: Can't use external
|
||||
players with this method.
|
||||
'''
|
||||
|
||||
def __init__(self, server):
|
||||
self.server = server
|
||||
super(QueuePlay, self).__init__()
|
||||
|
||||
def run(self):
|
||||
LOG.info('##===---- Starting QueuePlay ----===##')
|
||||
play_folder = False
|
||||
play = None
|
||||
start_position = None
|
||||
position = None
|
||||
|
||||
# Let Kodi catch up
|
||||
xbmc.sleep(200)
|
||||
|
||||
while True:
|
||||
|
||||
try:
|
||||
try:
|
||||
params = self.server.queue.get(timeout=0.01)
|
||||
except Queue.Empty:
|
||||
count = 20
|
||||
while not utils.window('plex.playlist.ready.bool'):
|
||||
xbmc.sleep(50)
|
||||
if not count:
|
||||
LOG.info('Playback aborted')
|
||||
raise Exception('PlaybackAborted')
|
||||
count -= 1
|
||||
LOG.info('Starting playback at position: %s', start_position)
|
||||
if play_folder:
|
||||
LOG.info('Start playing folder')
|
||||
xbmc.executebuiltin('Dialog.Close(busydialognocancel)')
|
||||
play.start_playback()
|
||||
else:
|
||||
utils.window('plex.playlist.play.bool', True)
|
||||
xbmc.sleep(1000)
|
||||
play.remove_from_playlist(start_position)
|
||||
break
|
||||
play = PlayStrm(params, params.get('ServerId'))
|
||||
|
||||
if start_position is None:
|
||||
start_position = max(play.info['KodiPlaylist'].getposition(), 0)
|
||||
position = start_position + 1
|
||||
if play_folder:
|
||||
position = play.play_folder(position)
|
||||
else:
|
||||
if self.server.pending.count(params['plex_id']) != len(self.server.pending):
|
||||
play_folder = True
|
||||
utils.window('plex.playlist.start', str(start_position))
|
||||
position = play.play(position)
|
||||
if play_folder:
|
||||
xbmc.executebuiltin('Activateutils.window(busydialognocancel)')
|
||||
except Exception:
|
||||
utils.ERROR()
|
||||
play.info['KodiPlaylist'].clear()
|
||||
xbmc.Player().stop()
|
||||
self.server.queue.queue.clear()
|
||||
if play_folder:
|
||||
xbmc.executebuiltin('Dialog.Close(busydialognocancel)')
|
||||
else:
|
||||
utils.window('plex.playlist.aborted.bool', True)
|
||||
break
|
||||
self.server.queue.task_done()
|
||||
|
||||
utils.window('plex.playlist.ready', clear=True)
|
||||
utils.window('plex.playlist.start', clear=True)
|
||||
utils.window('plex.playlist.audio', clear=True)
|
||||
self.server.threads.remove(self)
|
||||
self.server.pending = []
|
||||
LOG.info('##===---- QueuePlay Stopped ----===##')
|
|
@ -29,11 +29,19 @@ PLEX_TYPE = None
|
|||
SECTION_ID = None
|
||||
APPEND_SHOW_TITLE = None
|
||||
APPEND_SXXEXX = None
|
||||
SYNCHED = True
|
||||
# Need to chain the PMS keys
|
||||
KEY = None
|
||||
|
||||
|
||||
def get_listitem(xml_element):
|
||||
"""
|
||||
Returns a valid xbmcgui.ListItem() for xml_element
|
||||
"""
|
||||
item = generate_item(xml_element)
|
||||
prepare_listitem(item)
|
||||
return create_listitem(item)
|
||||
|
||||
|
||||
def process_method_on_list(method_to_run, items):
|
||||
"""
|
||||
helper method that processes a method on each listitem with pooling if the
|
||||
|
@ -246,8 +254,6 @@ def attach_kodi_ids(xml):
|
|||
"""
|
||||
Attaches the kodi db_item to the xml's children, attribute 'pkc_db_item'
|
||||
"""
|
||||
if not SYNCHED:
|
||||
return
|
||||
with PlexDB(lock=False) as plexdb:
|
||||
for child in xml:
|
||||
api = API(child)
|
||||
|
|
Loading…
Reference in a new issue