Switch to stream playback, part II
This commit is contained in:
parent
7c6fdad770
commit
059ed7a5f0
13 changed files with 311 additions and 86 deletions
|
@ -72,10 +72,13 @@ class Movie(ItemBase):
|
|||
scraper='metadata.local')
|
||||
if do_indirect:
|
||||
# Set plugin path and media flags using real filename
|
||||
filename = api.file_name(force_first_media=True)
|
||||
path = 'plugin://%s.movies/' % v.ADDON_ID
|
||||
filename = ('%s?plex_id=%s&plex_type=%s&mode=play&filename=%s'
|
||||
% (path, plex_id, v.PLEX_TYPE_MOVIE, filename))
|
||||
path = 'http://127.0.0.1:%s/plex/kodi/movies/' % v.WEBSERVICE_PORT
|
||||
filename = '{0}/file.strm?kodi_id={1}&kodi_type={2}&plex_id={0}&plex_type={3}&name={4}'
|
||||
filename = filename.format(plex_id,
|
||||
kodi_id,
|
||||
v.KODI_TYPE_MOVIE,
|
||||
v.PLEX_TYPE_MOVIE,
|
||||
api.file_name(force_first_media=True))
|
||||
playurl = filename
|
||||
kodi_pathid = self.kodidb.get_path(path)
|
||||
|
||||
|
|
|
@ -5,11 +5,11 @@ from logging import getLogger
|
|||
from sqlite3 import IntegrityError
|
||||
|
||||
from . import common
|
||||
from .. import path_ops, timing, variables as v, app
|
||||
from .. import path_ops, timing, variables as v
|
||||
|
||||
LOG = getLogger('PLEX.kodi_db.video')
|
||||
|
||||
MOVIE_PATH = 'plugin://%s.movies/' % v.ADDON_ID
|
||||
MOVIE_PATH = 'http://127.0.0.1:%s/plex/kodi/movies/' % v.WEBSERVICE_PORT
|
||||
SHOW_PATH = 'plugin://%s.tvshows/' % v.ADDON_ID
|
||||
|
||||
|
||||
|
|
|
@ -449,6 +449,23 @@ def _playback_cleanup(ended=False):
|
|||
app.PLAYSTATE.active_players = set()
|
||||
LOG.info('Finished PKC playback cleanup')
|
||||
|
||||
def Playlist_OnAdd(self, server, data, *args, **kwargs):
|
||||
'''
|
||||
Detect widget playback. Widget for some reason, use audio playlists.
|
||||
'''
|
||||
LOG.debug('Playlist_OnAdd: %s, %s', server, data)
|
||||
if data['position'] == 0:
|
||||
if data['playlistid'] == 0:
|
||||
utils.window('plex.playlist.audio', value='true')
|
||||
else:
|
||||
utils.window('plex.playlist.audio', clear=True)
|
||||
self.playlistid = data['playlistid']
|
||||
if utils.window('plex.playlist.start') and data['position'] == int(utils.window('plex.playlist.start')) + 1:
|
||||
|
||||
LOG.info("--[ playlist ready ]")
|
||||
utils.window('plex.playlist.ready', value='true')
|
||||
utils.window('plex.playlist.start', clear=True)
|
||||
|
||||
|
||||
def _record_playstate(status, ended):
|
||||
if not status['plex_id']:
|
||||
|
|
|
@ -3,11 +3,12 @@ from __future__ import absolute_import, division, unicode_literals
|
|||
from logging import getLogger
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
|
||||
from .plex_api import API
|
||||
from . import plex_function as PF, utils, json_rpc, variables as v, \
|
||||
widgets
|
||||
from .playutils import PlayUtils
|
||||
from .windows.resume import resume_dialog
|
||||
from . import app, plex_functions as PF, utils, json_rpc, variables as v, \
|
||||
widgets, playlist_func as PL, playqueue as PQ
|
||||
|
||||
|
||||
LOG = getLogger('PLEX.playstrm')
|
||||
|
@ -51,11 +52,13 @@ class PlayStrm(object):
|
|||
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)
|
||||
if utils.window('plex.playlist.audio'):
|
||||
LOG.debug('Audio playlist detected')
|
||||
self.playqueue = PQ.get_playqueue_from_type(v.KODI_TYPE_AUDIO)
|
||||
else:
|
||||
self.kodi_playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||
LOG.debug('Video playlist detected')
|
||||
self.playqueue = PQ.get_playqueue_from_type(v.KODI_TYPE_VIDEO)
|
||||
self.kodi_playlist = self.playqueue.kodi_pl
|
||||
|
||||
def __repr__(self):
|
||||
return ("{{"
|
||||
|
@ -97,6 +100,7 @@ class PlayStrm(object):
|
|||
else:
|
||||
self.xml[0].set('pkc_db_item', None)
|
||||
self.api = API(self.xml[0])
|
||||
self.playqueue_item = PL.playlist_item_from_xml(self.xml[0])
|
||||
|
||||
def start_playback(self, index=0):
|
||||
LOG.debug('Starting playback at %s', index)
|
||||
|
@ -111,7 +115,7 @@ class PlayStrm(object):
|
|||
else:
|
||||
self.start_index = max(self.kodi_playlist.getposition(), 0)
|
||||
self.index = self.start_index
|
||||
listitem = xbmcgui.ListItem()
|
||||
listitem = widgets.get_listitem(self.xml[0])
|
||||
self._set_playlist(listitem)
|
||||
LOG.info('Initiating play for %s', self)
|
||||
if not delayed:
|
||||
|
@ -159,24 +163,41 @@ class PlayStrm(object):
|
|||
action set in Kodi for accurate resume behavior.
|
||||
'''
|
||||
seektime = self._resume()
|
||||
trailers = False
|
||||
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)
|
||||
if utils.settings('askCinema') == "true":
|
||||
# "Play trailers?"
|
||||
trailers = utils.yesno_dialog(utils.lang(29999),
|
||||
utils.lang(33016)) or False
|
||||
else:
|
||||
trailers = True
|
||||
LOG.debug('Playing trailers: %s', trailers)
|
||||
xml = PF.init_plex_playqueue(self.plex_id,
|
||||
self.xml.get('librarySectionUUID'),
|
||||
mediatype=self.plex_type,
|
||||
trailers=trailers)
|
||||
if xml is None:
|
||||
LOG.error('Could not get playqueue for UUID %s for %s',
|
||||
self.xml.get('librarySectionUUID'), self)
|
||||
# "Play error"
|
||||
utils.dialog('notification',
|
||||
utils.lang(29999),
|
||||
utils.lang(30128),
|
||||
icon='{error}')
|
||||
app.PLAYSTATE.context_menu_play = False
|
||||
app.PLAYSTATE.force_transcode = False
|
||||
app.PLAYSTATE.resume_playback = False
|
||||
return
|
||||
PL.get_playlist_details_from_xml(self.playqueue, xml)
|
||||
# See that we add trailers, if they exist in the xml return
|
||||
self._set_intros(xml)
|
||||
listitem.setSubtitles(self.api.cache_external_subs())
|
||||
play = PlayUtils(self.api, self.playqueue_item)
|
||||
url = play.getPlayUrl().encode('utf-8')
|
||||
listitem.setPath(url)
|
||||
self.kodi_playlist.add(url=url, listitem=listitem, index=self.index)
|
||||
self.index += 1
|
||||
|
||||
if self.xml.get('PartCount'):
|
||||
self._set_additional_parts()
|
||||
|
||||
|
@ -203,52 +224,41 @@ class PlayStrm(object):
|
|||
utils.window('plex.autoplay.bool', value='true')
|
||||
return seektime
|
||||
|
||||
def _set_intros(self):
|
||||
def _set_intros(self, xml):
|
||||
'''
|
||||
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')
|
||||
if not len(xml) > 1:
|
||||
LOG.debug('No trailers returned from the PMS')
|
||||
return
|
||||
for intro in xml:
|
||||
if utils.cast(int, xml.get('ratingKey')) == self.plex_id:
|
||||
# The main item we're looking at - skip!
|
||||
continue
|
||||
api = API(intro)
|
||||
listitem = widgets.get_listitem(intro)
|
||||
listitem.setSubtitles(api.cache_external_subs())
|
||||
playqueue_item = PL.playlist_item_from_xml(intro)
|
||||
play = PlayUtils(api, playqueue_item)
|
||||
url = play.getPlayUrl().encode('utf-8')
|
||||
listitem.setPath(url)
|
||||
self.kodi_playlist.add(url=url, listitem=listitem, index=self.index)
|
||||
self.index += 1
|
||||
utils.window('plex.skip.%s' % api.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)
|
||||
for part, _ in enumerate(self.xml[0][0]):
|
||||
if part == 0:
|
||||
# The first part that we've already added
|
||||
continue
|
||||
self.api.set_part_number(part)
|
||||
listitem = widgets.get_listitem(self.xml[0])
|
||||
listitem.setSubtitles(self.api.cache_external_subs())
|
||||
playqueue_item = PL.playlist_item_from_xml(self.xml[0])
|
||||
play = PlayUtils(self.api, playqueue_item)
|
||||
url = play.getPlayUrl().encode('utf-8')
|
||||
listitem.setPath(url)
|
||||
self.kodi_playlist.add(url=url, listitem=listitem, index=self.index)
|
||||
self.index += 1
|
||||
|
|
|
@ -33,7 +33,7 @@ class WebService(backgroundthread.KillableThread):
|
|||
s.connect(('127.0.0.1', v.WEBSERVICE_PORT))
|
||||
s.sendall('')
|
||||
except Exception as error:
|
||||
LOG.error(error)
|
||||
LOG.error('is_alive error: %s', error)
|
||||
if 'Errno 61' in str(error):
|
||||
alive = False
|
||||
s.close()
|
||||
|
@ -47,12 +47,12 @@ class WebService(backgroundthread.KillableThread):
|
|||
conn.request('QUIT', '/')
|
||||
conn.getresponse()
|
||||
except Exception:
|
||||
pass
|
||||
utils.ERROR()
|
||||
|
||||
def run(self):
|
||||
''' Called to start the webservice.
|
||||
'''
|
||||
LOG.info('----===## Starting Webserver on port %s ##===----',
|
||||
LOG.info('----===## Starting WebService on port %s ##===----',
|
||||
v.WEBSERVICE_PORT)
|
||||
app.APP.register_thread(self)
|
||||
try:
|
||||
|
@ -60,11 +60,12 @@ class WebService(backgroundthread.KillableThread):
|
|||
RequestHandler)
|
||||
server.serve_forever()
|
||||
except Exception as error:
|
||||
LOG.error('Error encountered: %s', error)
|
||||
if '10053' not in error: # ignore host diconnected errors
|
||||
utils.ERROR()
|
||||
finally:
|
||||
app.APP.deregister_thread(self)
|
||||
LOG.info('##===---- Webserver stopped ----===##')
|
||||
LOG.info('##===---- WebService stopped ----===##')
|
||||
|
||||
|
||||
class HttpServer(BaseHTTPServer.HTTPServer):
|
||||
|
@ -75,7 +76,7 @@ class HttpServer(BaseHTTPServer.HTTPServer):
|
|||
self.pending = []
|
||||
self.threads = []
|
||||
self.queue = Queue.Queue()
|
||||
super(HttpServer, self).__init__(*args, **kwargs)
|
||||
BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
|
||||
|
||||
def serve_forever(self):
|
||||
|
||||
|
@ -86,8 +87,9 @@ class HttpServer(BaseHTTPServer.HTTPServer):
|
|||
|
||||
|
||||
class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
''' Http request handler. Do not use LOG here,
|
||||
it will hang requests in Kodi > show information dialog.
|
||||
'''
|
||||
Http request handler. Do not use LOG here, it will hang requests in Kodi >
|
||||
show information dialog.
|
||||
'''
|
||||
timeout = 0.5
|
||||
|
||||
|
@ -101,8 +103,8 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
|||
'''
|
||||
try:
|
||||
BaseHTTPServer.BaseHTTPRequestHandler.handle(self)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as error:
|
||||
xbmc.log('Plex.WebService handle error: %s' % error, xbmc.LOGWARNING)
|
||||
|
||||
def do_QUIT(self):
|
||||
''' send 200 OK response, and set server.stop to True
|
||||
|
@ -142,6 +144,7 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
|||
def handle_request(self, headers_only=False):
|
||||
'''Send headers and reponse
|
||||
'''
|
||||
xbmc.log('Plex.WebService handle_request called. path: %s ]' % self.path, xbmc.LOGWARNING)
|
||||
try:
|
||||
if b'extrafanart' in self.path or b'extrathumbs' in self.path:
|
||||
raise Exception('unsupported artwork request')
|
||||
|
@ -161,7 +164,6 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
|||
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.
|
||||
|
@ -268,7 +270,7 @@ class QueuePlay(backgroundthread.KillableThread):
|
|||
params = self.server.queue.get(timeout=0.01)
|
||||
except Queue.Empty:
|
||||
count = 20
|
||||
while not utils.window('plex.playlist.ready.bool'):
|
||||
while not utils.window('plex.playlist.ready'):
|
||||
xbmc.sleep(50)
|
||||
if not count:
|
||||
LOG.info('Playback aborted')
|
||||
|
@ -280,14 +282,14 @@ class QueuePlay(backgroundthread.KillableThread):
|
|||
xbmc.executebuiltin('Dialog.Close(busydialognocancel)')
|
||||
play.start_playback()
|
||||
else:
|
||||
utils.window('plex.playlist.play.bool', True)
|
||||
utils.window('plex.playlist.play', value='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)
|
||||
start_position = max(play.kodi_playlist.getposition(), 0)
|
||||
position = start_position + 1
|
||||
if play_folder:
|
||||
position = play.play_folder(position)
|
||||
|
@ -300,13 +302,13 @@ class QueuePlay(backgroundthread.KillableThread):
|
|||
xbmc.executebuiltin('Activateutils.window(busydialognocancel)')
|
||||
except Exception:
|
||||
utils.ERROR()
|
||||
play.info['KodiPlaylist'].clear()
|
||||
play.kodi_playlist.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)
|
||||
utils.window('plex.playlist.aborted', value='true')
|
||||
break
|
||||
self.server.queue.task_done()
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ def get_listitem(xml_element):
|
|||
"""
|
||||
item = generate_item(xml_element)
|
||||
prepare_listitem(item)
|
||||
return create_listitem(item)
|
||||
return create_listitem(item, as_tuple=False)
|
||||
|
||||
|
||||
def process_method_on_list(method_to_run, items):
|
||||
|
|
81
resources/lib/windows/resume.py
Normal file
81
resources/lib/windows/resume.py
Normal file
|
@ -0,0 +1,81 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, division, unicode_literals
|
||||
from datetime import timedelta
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
|
||||
from logging import getLogger
|
||||
|
||||
|
||||
LOG = getLogger('PLEX.resume')
|
||||
|
||||
XML_PATH = (xbmcaddon.Addon('plugin.video.plexkodiconnect').getAddonInfo('path'),
|
||||
"default",
|
||||
"1080i")
|
||||
|
||||
ACTION_PARENT_DIR = 9
|
||||
ACTION_PREVIOUS_MENU = 10
|
||||
ACTION_BACK = 92
|
||||
RESUME = 3010
|
||||
START_BEGINNING = 3011
|
||||
|
||||
|
||||
class ResumeDialog(xbmcgui.WindowXMLDialog):
|
||||
|
||||
_resume_point = None
|
||||
selected_option = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs)
|
||||
|
||||
def set_resume_point(self, time):
|
||||
self._resume_point = time
|
||||
|
||||
def is_selected(self):
|
||||
return True if self.selected_option is not None else False
|
||||
|
||||
def get_selected(self):
|
||||
return self.selected_option
|
||||
|
||||
def onInit(self):
|
||||
|
||||
self.getControl(RESUME).setLabel(self._resume_point)
|
||||
self.getControl(START_BEGINNING).setLabel(xbmc.getLocalizedString(12021))
|
||||
|
||||
def onAction(self, action):
|
||||
if action in (ACTION_BACK, ACTION_PARENT_DIR, ACTION_PREVIOUS_MENU):
|
||||
self.close()
|
||||
|
||||
def onClick(self, controlID):
|
||||
if controlID == RESUME:
|
||||
self.selected_option = 1
|
||||
self.close()
|
||||
if controlID == START_BEGINNING:
|
||||
self.selected_option = 0
|
||||
self.close()
|
||||
|
||||
|
||||
def resume_dialog(seconds):
|
||||
'''
|
||||
Base resume dialog based on Kodi settings
|
||||
Returns True if PKC should resume, False if not, None if user backed out
|
||||
of the dialog
|
||||
'''
|
||||
LOG.info("Resume dialog called")
|
||||
dialog = ResumeDialog("script-plex-resume.xml", *XML_PATH)
|
||||
dialog.set_resume_point("Resume from %s"
|
||||
% unicode(timedelta(seconds=seconds)).split(".")[0])
|
||||
dialog.doModal()
|
||||
|
||||
if dialog.is_selected():
|
||||
if not dialog.get_selected():
|
||||
# Start from beginning selected
|
||||
return False
|
||||
else:
|
||||
# User backed out
|
||||
LOG.info("User exited without a selection")
|
||||
return
|
||||
return True
|
112
resources/skins/default/1080i/script-plex-resume.xml
Normal file
112
resources/skins/default/1080i/script-plex-resume.xml
Normal file
|
@ -0,0 +1,112 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<window id="3301" type="dialog">
|
||||
<defaultcontrol always="true">100</defaultcontrol>
|
||||
<controls>
|
||||
<control type="group">
|
||||
<control type="image">
|
||||
<top>0</top>
|
||||
<bottom>0</bottom>
|
||||
<left>0</left>
|
||||
<right>0</right>
|
||||
<texture colordiffuse="CC000000">white.png</texture>
|
||||
<aspectratio>stretch</aspectratio>
|
||||
<animation effect="fade" end="100" time="200">WindowOpen</animation>
|
||||
<animation effect="fade" start="100" end="0" time="200">WindowClose</animation>
|
||||
</control>
|
||||
<control type="group">
|
||||
<animation effect="slide" time="0" end="0,-15" condition="true">Conditional</animation>
|
||||
<animation type="WindowOpen" reversible="false">
|
||||
<effect type="zoom" start="80" end="100" center="960,540" delay="160" tween="circle" easin="out" time="240" />
|
||||
<effect type="fade" delay="160" end="100" time="240" />
|
||||
</animation>
|
||||
<animation type="WindowClose" reversible="false">
|
||||
<effect type="zoom" start="100" end="80" center="960,540" easing="in" tween="circle" easin="out" time="240" />
|
||||
<effect type="fade" start="100" end="0" time="240" />
|
||||
</animation>
|
||||
<centerleft>50%</centerleft>
|
||||
<centertop>50%</centertop>
|
||||
<width>20%</width>
|
||||
<height>90%</height>
|
||||
<control type="grouplist" id="100">
|
||||
<orientation>vertical</orientation>
|
||||
<left>0</left>
|
||||
<right>0</right>
|
||||
<height>auto</height>
|
||||
<align>center</align>
|
||||
<itemgap>0</itemgap>
|
||||
<onright>close</onright>
|
||||
<onleft>close</onleft>
|
||||
<usecontrolcoords>true</usecontrolcoords>
|
||||
<control type="group">
|
||||
<height>30</height>
|
||||
<control type="image">
|
||||
<left>20</left>
|
||||
<width>100%</width>
|
||||
<height>25</height>
|
||||
<texture>logo-white.png</texture>
|
||||
<aspectratio align="left">keep</aspectratio>
|
||||
</control>
|
||||
<control type="image">
|
||||
<right>20</right>
|
||||
<width>100%</width>
|
||||
<height>25</height>
|
||||
<aspectratio align="right">keep</aspectratio>
|
||||
<texture diffuse="user_image.png">$INFO[Window(Home).Property(EmbyUserImage)]</texture>
|
||||
<visible>!String.IsEmpty(Window(Home).Property(EmbyUserImage))</visible>
|
||||
</control>
|
||||
<control type="image">
|
||||
<right>20</right>
|
||||
<width>100%</width>
|
||||
<height>25</height>
|
||||
<aspectratio align="right">keep</aspectratio>
|
||||
<texture diffuse="user_image.png">userflyoutdefault.png</texture>
|
||||
<visible>String.IsEmpty(Window(Home).Property(EmbyUserImage))</visible>
|
||||
</control>
|
||||
</control>
|
||||
<control type="image">
|
||||
<width>100%</width>
|
||||
<height>10</height>
|
||||
<texture border="5" colordiffuse="ff222326">dialogs/menu_top.png</texture>
|
||||
</control>
|
||||
<control type="button" id="3010">
|
||||
<width>100%</width>
|
||||
<height>65</height>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textoffsetx>20</textoffsetx>
|
||||
<font>font13</font>
|
||||
<textcolor>ffe1e1e1</textcolor>
|
||||
<focusedcolor>ffe1e1e1</focusedcolor>
|
||||
<shadowcolor>66000000</shadowcolor>
|
||||
<disabledcolor>FF404040</disabledcolor>
|
||||
<texturefocus border="10" colordiffuse="ff303034">dialogs/menu_back.png</texturefocus>
|
||||
<texturenofocus border="10" colordiffuse="ff222326">dialogs/menu_back.png</texturenofocus>
|
||||
<alttexturefocus border="10" colordiffuse="ff303034">dialogs/menu_back.png</alttexturefocus>
|
||||
<alttexturenofocus border="10" colordiffuse="ff222326">dialogs/menu_back.png</alttexturenofocus>
|
||||
</control>
|
||||
<control type="button" id="3011">
|
||||
<width>100%</width>
|
||||
<height>65</height>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textoffsetx>20</textoffsetx>
|
||||
<font>font13</font>
|
||||
<textcolor>ffe1e1e1</textcolor>
|
||||
<focusedcolor>ffe1e1e1</focusedcolor>
|
||||
<shadowcolor>66000000</shadowcolor>
|
||||
<disabledcolor>FF404040</disabledcolor>
|
||||
<texturefocus border="10" colordiffuse="ff303034">dialogs/menu_back.png</texturefocus>
|
||||
<texturenofocus border="10" colordiffuse="ff222326">dialogs/menu_back.png</texturenofocus>
|
||||
<alttexturefocus border="10" colordiffuse="ff303034">dialogs/menu_back.png</alttexturefocus>
|
||||
<alttexturenofocus border="10" colordiffuse="ff222326">dialogs/menu_back.png</alttexturenofocus>
|
||||
</control>
|
||||
<control type="image">
|
||||
<width>100%</width>
|
||||
<height>10</height>
|
||||
<texture border="5" colordiffuse="ff222326">dialogs/menu_bottom.png</texture>
|
||||
</control>
|
||||
</control>
|
||||
</control>
|
||||
</control>
|
||||
</controls>
|
||||
</window>
|
BIN
resources/skins/default/media/dialogs/dialog_back.png
Normal file
BIN
resources/skins/default/media/dialogs/dialog_back.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
resources/skins/default/media/dialogs/menu_back.png
Normal file
BIN
resources/skins/default/media/dialogs/menu_back.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
resources/skins/default/media/dialogs/menu_bottom.png
Normal file
BIN
resources/skins/default/media/dialogs/menu_bottom.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
resources/skins/default/media/dialogs/menu_top.png
Normal file
BIN
resources/skins/default/media/dialogs/menu_top.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
resources/skins/default/media/dialogs/white.jpg
Normal file
BIN
resources/skins/default/media/dialogs/white.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.9 KiB |
Loading…
Reference in a new issue