diff --git a/kivyblocks/blocksapp.py b/kivyblocks/blocksapp.py index 0c1d6bb..bef19ca 100644 --- a/kivyblocks/blocksapp.py +++ b/kivyblocks/blocksapp.py @@ -28,6 +28,32 @@ from .utils import * from .pagescontainer import PageContainer from .widgetExt.messager import Messager from .blocks import Blocks +from appPublic.rsa import RSA + +class ServerInfo: + def __init__(self): + self.rsaEngine = RSA() + self.publickey = None + self.authCode = None + + def getServerPK(self): + config = getConfig() + url = '%s%s' % (config.uihome, config.publickey_url) + hc = App.get_running_app().hc + d = hc.get(url) + self.publickey = self.rsaEngine. publickeyFromText(d) + + def encode(self,uinfo): + if self.publickey is None: + self.getServerPK() + + if uinfo['authmethod'] == 'password': + authinfo = '%s::%s::%s' % (uinfo['authmethod'], uinfo['userid'], uinfo['passwd']) + x = self.rsaEngine.encode(self.publickey, authinfo) + self.authCode = x + return x + return None + def signal_handler(signal, frame): app = App.get_running_app() @@ -42,7 +68,7 @@ class BlocksApp(App): def build(self): config = getConfig() self.config = config - self.userinfo = {} + self.serverinfo = ServerInfo() self.title = 'Test Title' self.blocks = Blocks() self.workers = Workers(maxworkers=config.maxworkers or 80) @@ -55,6 +81,18 @@ class BlocksApp(App): print('build() called......') return x + def getAuthHeader(self): + if not hasattr(self,'serverinfo'): + print('app serverinfo not found') + return {} + serverinfo = self.serverinfo + if hasattr(serverinfo,'authCode'): + return { + 'authorization':serverinfo.authCode + } + print('serverinfo authCode not found') + return {} + def on_start(self): print('on_start() called ...') diff --git a/kivyblocks/form.py b/kivyblocks/form.py index a23ec69..c3b2a73 100644 --- a/kivyblocks/form.py +++ b/kivyblocks/form.py @@ -210,6 +210,7 @@ class Form(BoxLayout): self.inputwidth = Window.width / self.cols self.inputheight = CSize(self.options.get('inputheight',3)) self.initflag = False + self.register_event_type('on_submit') self.bind(size=self.on_size, pos=self.on_size) @@ -224,7 +225,6 @@ class Form(BoxLayout): print('box_width=%d,cols=%d' % (self.inputwidth, self.cols)) self.add_widget(self.toolbar) self.add_widget(self.fsc) - self.register_event_type('on_submit') self.fieldWidgets=[] for f in self.options['fields']: w = InputBox(self, **f) @@ -232,12 +232,11 @@ class Form(BoxLayout): self.fsc.add_widget(w) self.fieldWidgets.append(w) blocks = App.get_running_app().blocks - wid = self.widget_ids['__submit'] - # wid = getWidgetById(self,'__submit') - # print('ids=',self.widget_ids.keys()) + # wid = self.widget_ids['__submit'] + wid = blocks.getWidgetByIdPath(self,'__submit') wid.bind(on_press=self.on_submit_button) - # wid = getWidgetById(self,'__clear') - wid = self.widget_ids['__clear'] + wid = blocks.getWidgetByIdPath(self,'__clear') + # wid = self.widget_ids['__clear'] wid.bind(on_press=self.on_clear_button) self.initflag = True @@ -248,12 +247,13 @@ class Form(BoxLayout): d.update(v) return d - def on_submit(self,o,v=None): - print(o,v) + def on_submit(self,v=None): + print('Form():on_submit fired ...',v) + return False def on_submit_button(self,o,v=None): d = self.getData() - self.dispatch('on_submit',self,d) + self.dispatch('on_submit',d) def on_clear_button(self,o,v=None): for fw in self.fieldWidgets: @@ -291,6 +291,6 @@ class StrSearchForm(BoxLayout): } self.dispatch('on_submit',d) - def on_submit(self,o,v=None): + def on_submit(self,v=None): print('StrSearchForm():on_submit fired ..........') diff --git a/kivyblocks/login.py b/kivyblocks/login.py index 9dbbb7b..a23101c 100644 --- a/kivyblocks/login.py +++ b/kivyblocks/login.py @@ -2,34 +2,15 @@ from kivy.app import App from kivy.core.window import Window from kivy.uix.popup import Popup from appPublic.Singleton import SingletonDecorator +from appPublic.jsonConfig import getConfig from appPublic.registerfunction import RegisterFunction -from appPublic.rsa import RSA -# from .form import Form - -class ServerInfo: - def __init__(self): - self.rsaEngine = RSA() - config = getConfig() - url = '%s%s' % (config.uihome, config.publickey_url) - hc = App.get_running_app().hc - d = hc.get(url) - self.publickey = self.rsaEngine. publickeyFromText(d) - - def encode(self,uinfo): - if uinfo['authmethod'] == 'password': - authinfo = '%s::%s::%s' % (uinfo['authmethod'], uinfo['userid'], uinfo['password']) - txt = self.serverinfo.encode(authinfo) - x = self.rsaEngine.encode(self.publickey, txt) - return x - return None - logformdesc = { "widgettype":"Form", "options":{ "cols":1, - "labelwidth":0.3, + "labelwidth":0.4, "textsize":1, - "inputheight":3, + "inputheight":4, "fields":[ { "name":"userid", @@ -44,61 +25,43 @@ logformdesc = { "uitype":"password" } ] - }, - "binds":[ - { - "wid":"self", - "event":"on_submit", - "datawidegt":"self", - "actiontype":"registedfunction", - "rfname":"setupUserInfo" - } - ] + } } @SingletonDecorator class LoginForm(Popup): def __init__(self): super().__init__(size_hint=(0.8,0.8)) - - app = App.get_running_app() - print('here ..1............') - self.content = app.blocks.widgetBuild(logformdesc) - # self.content.bind(on_submit=setipUserInfo) - print('here ..2............',self.content) self.title = 'login' - self.blockCalls = [] - self.open_status = False - print('here ..3............') + self.initflag = False + self.bind(size=self.buildContent, + pos=self.buildContent) + + def buildContent(self,o,size): + print('build Content. ....') + if self.initflag: + return + print('build Content ....... ....') + self.initflag = True + app = App.get_running_app() + self.content = app.blocks.widgetBuild(logformdesc) self.content.bind(on_submit=self.on_submit) - self.content.bind(on_cancel=self.on_submit) - print('here ..4............') - self.open() - def close(self): - self.open_status = False + def on_submit(self,o,userinfo): + print('login(),on_submit fired ....') self.dismiss() + print('userinfo=',userinfo) + app = App.get_running_app() - def on_submit(self,o,v): - self.dismiss() + if userinfo.get('passwd',False): + userinfo['authmethod'] = 'password' + authinfo = app.serverinfo.encode(userinfo) + login_url = '%s%s' % (app.config.uihome, app.config.login_url) + x = app.hc.get(login_url) + print('login return=', x, login_url, authinfo) def on_cancel(self,o,v): + print('login() on_cancel fired .....') self.dismiss() -def setupUserInfo(obj, userinfo): - app = App.get_running_app() - if not hasattr(app, 'serverinfo'): - app.serverinfo = ServerInfo() - - if userinfo.get('password',False): - userinfo['authmethod'] = 'password' - authinfo = app.serverinfo.encode(userinfo) - headers = { - "authorization":authinfo - } - login_url = '%s%s' % (app.config.uihome, app.config.login_url) - app.hc.get(login_url,headers=headers) - -rf = RegisterFunction() -rf.register('setupUserInfo',setupUserInfo) diff --git a/kivyblocks/responsivelayout.py b/kivyblocks/responsivelayout.py index 7801fa2..d01bffe 100644 --- a/kivyblocks/responsivelayout.py +++ b/kivyblocks/responsivelayout.py @@ -51,7 +51,11 @@ class VResponsiveLayout(ScrollView): def setCols(self,t=None): self.box_width = self.org_box_width - self.cols = int(self.width / self.box_width) + if self.width < self.box_width: + self.cols = self.org_cols + else: + self.cols = int(self.width / self.box_width) + print('VResponsiveLayout()::setCols():self.cols=',self.cols, 'self.box_width=',self.box_width,'self.org_cols=',self.org_cols) if isHandHold(): w,h = self.size if w < h: diff --git a/kivyblocks/threadcall.py b/kivyblocks/threadcall.py index 2f55175..3151966 100644 --- a/kivyblocks/threadcall.py +++ b/kivyblocks/threadcall.py @@ -42,7 +42,7 @@ class ThreadCall(Thread,EventDispatcher): pass def checkStop(self,timestamp): - x = self.join(timeout=0.0001) + x = self.join(timeout=0.001) if self.is_alive(): self.timing = Clock.schedule_once(self.checkStop,0) return @@ -79,12 +79,28 @@ class Workers(Thread): with self.lock: self.tasks.insert(0,[callee,callback,errback,kwargs]) +hostsessions = {} + class HttpClient: def __init__(self): self.s = requests.Session() self.workers = App.get_running_app().workers + def url2domain(self,url): + parts = url.split('/')[:3] + pre = '/'.join(parts) + return pre + def webcall(self,url,method="GET",params={},files={},headers={}): + app = App.get_running_app() + domain = self.url2domain(url) + sessionid = hostsessions.get(domain,None) + print('hostsessions=', hostsessions, 'sessionid=',sessionid, 'domain=',domain) + if sessionid: + headers.update({'session':sessionid}) + elif app.getAuthHeader(): + headers.update(app.getAuthHeader()) + print('headers=',headers) if method in ['GET']: req = requests.Request(method,url, params=params,headers=headers) @@ -94,6 +110,11 @@ class HttpClient: prepped = self.s.prepare_request(req) resp = self.s.send(prepped) if resp.status_code == 200: + h = resp.headers.get('Set-Cookie',None) + if h: + print('*************set-Cookie=',h,'domain=',domain) + sessionid = h.split(';')[0] + hostsessions[domain] = sessionid try: data = resp.json() if type(data) != type({}): @@ -128,7 +149,7 @@ class HttpClient: except Exception as e: if errback is not None: errback(e) - + return None kwargs = { "url":url, "method":method, diff --git a/kivyblocks/vplayer.py b/kivyblocks/vplayer.py index 516f5cf..fc12fa4 100644 --- a/kivyblocks/vplayer.py +++ b/kivyblocks/vplayer.py @@ -1,5 +1,6 @@ import os import sys +import time from kivy.utils import platform from traceback import print_exc from kivy.core.window import Window @@ -15,6 +16,7 @@ from kivy.app import App from kivy.clock import Clock from kivy.properties import ObjectProperty, StringProperty, BooleanProperty, \ NumericProperty, DictProperty, OptionProperty +from pythonosc import dispatcher, osc_server from .utils import * from .baseWidget import PressableImage @@ -26,79 +28,26 @@ desktopOSs=[ othersplatforms=['ios','android'] -class VPlayer(FloatLayout): +class BaseVPlayer(FloatLayout): fullscreen = BooleanProperty(False) - exit = BooleanProperty(False) - stoped_play = BooleanProperty(False) - paused_play = BooleanProperty(False) - - def __init__(self,vfile=None, - playlist=None, - loop=False, - openfile_img=None, - exit_img = None, - pause_img = None, - play_img = None, - mute_img = None, - track_img = None, - next_img = None, - replay_img = None, - can_openfile=False, - can_cut=False, - can_replay=False, - can_changevolume=True - ): + def __init__(self,vfile=None): super().__init__() Window.allow_screensaver = False - print(self,vfile) self._video = Video(allow_stretch=True,pos_hint={'x': 0, 'y': 0},size_hint=(1,1)) self.add_widget(self._video) - self.loop = loop - self.openfile_img = openfile_img - self.can_openfile = can_openfile - self.can_replay = can_replay - self.can_cut = can_cut - self.can_changevolume = can_changevolume - if self.openfile_img: - self.can_openfile = True - self.exit_img = exit_img - self.play_img = play_img - self.pause_img = pause_img - self.mute_img = mute_img - self.track_img = track_img - self.next_img = next_img - self.replay_img = replay_img self.ffplayer = None - self.menubar = None - self._popup = None - self.menu_status = False - self.manualMode = False - self.update_task = None - self.old_path = os.getcwd() - self.pb = None - if vfile: - if type(vfile) == type([]): - self.playlist = vfile - else: - self.playlist = [vfile] - self.curplay = 0 - self.play() + if type(vfile) == type([]): + self.playlist = vfile else: - self.playlist = [] - self.curplay = -1 + self.playlist = [vfile] + self.curplay = 0 + self.play() self._video.bind(eos=self.video_end) self._video.bind(state=self.on_state) - self._video.bind(loaded=self.createProgressbar) - self._video.bind(on_touch_down=self.show_hide_menu) - self.register_event_type('on_playend') def __del__(self): - print('********** delete VPlayer instance ********') if self._video is None: return - if self.update_task: - self.update_task.cancel() - self.update_task = None self._video.state = 'pause' Window.allow_screensaver = True del self._video @@ -117,75 +66,10 @@ class VPlayer(FloatLayout): def video_end(self,t,v): self.curplay += 1 - if not self.loop and self.curplay >= len(self.playlist): - self.dispatch('on_playend') - return self.curplay = self.curplay % len(self.playlist) self._video.source = self.playlist[self.curplay] self._video.state = 'play' - def totime(self,dur): - h = dur / 3600 - m = dur % 3600 / 60 - s = dur % 60 - return '%02d:%02d:%02d' % (h,m,s) - - def createProgressbar(self,obj,v): - if hasattr(self._video._video, '_ffplayer'): - self.ffplayer = self._video._video._ffplayer - - if self.pb is None: - self.pb = BoxLayout(orientation='horizontal', - size_hint = (0.99,None),height=CSize(1.4)) - self.curposition = Label(text='0',width=CSize(4), - size_hint_x=None) - self.curposition.align='right' - self.maxposition = Label(text=self.totime(self._video.duration), - width=CSize(4),size_hint_x=None) - self.maxposition.align = 'left' - self.slider = Slider(min=0, - max=self._video.duration, - value=0, - orientation='horizontal', - step=0.01) - self.slider.bind(on_touch_down=self.enterManualMode) - self.slider.bind(on_touch_up=self.endManualMode) - self.manual_mode=False - - self.pb.add_widget(self.curposition) - self.pb.add_widget(self.slider) - self.pb.add_widget(self.maxposition) - self.update_task = Clock.schedule_interval(self.update_slider,1) - self.buildMenu() - - def enterManualMode(self,obj,touch): - if not self.slider.collide_point(*touch.pos): - return - self.manualMode = True - - def endManualMode(self,obj,touch): - if not self.manualMode: - return - if self._video.duration < 0.0001: - return - self._video.seek(self.slider.value/self._video.duration) - self.manualMode = False - - def update_slider(self,t): - self.curposition.text = self.totime(self._video.position) - if not self.manualMode: - self.slider.value = self._video.position - self.slider.max = self._video.duration - self.maxposition.text = self.totime(self._video.duration) - - def beforeDestroy(self): - try: - del self - - except Exception as e: - print_exc() - return True - def on_state(self,o,v): if self._video.state == 'play': Window.allow_screensaver = False @@ -212,7 +96,6 @@ class VPlayer(FloatLayout): if value: Window.fullscreen = True - print('Window size=',Window.size) self._fullscreen_state = state = { 'parent': self.parent, 'pos': self.pos, @@ -221,7 +104,6 @@ class VPlayer(FloatLayout): 'size_hint': self.size_hint, 'window_children': window.children[:]} - print('vplayer fullscreen,platform=',platform,desktopOSs) if platform in desktopOSs: Window.maximize() # remove all window children @@ -251,10 +133,182 @@ class VPlayer(FloatLayout): self.size = state['size'] if state['parent'] is not window: state['parent'].add_widget(self) - print('vplayer fullscreen,platform=',platform,desktopOSs) if platform in desktopOSs: Window.restore() + def endplay(self,btn): + self._video.seek(1.0,precise=True) + + def replay(self,btn): + self._video.seek(0.0,precise=True) + + def audioswitch(self,btn): + if self.ffplayer is not None: + self.ffplayer.request_channel('audio') + + def setVolume(self,obj,v): + self._video.volume = v + + def setPosition(self,obj,v): + self._video.seek(v) + + def mute(self,btn): + if self._video.volume > 0.001: + self.old_volume = self._video.volume + self._video.volume = 0.0 + else: + self._video.volume = self.old_volume + + def stop(self): + try: + self._video.state = 'stop' + except: + print_exc() + + def pause(self,t=None): + if self._video.state == 'play': + self._video.state = 'pause' + else: + self._video.state = 'play' + +class OSCVPlayer(BaseVPlayer): + def __init__(self,ip,port,vfile=None): + self.ip = ip + self.port = port + self.dispatcher = dispatcher.Dispatcher() + self.server = osc_server.ThreadingOSCUDPServer( (self.ip, self.port), self.dispatcher) + BaseVPlayer.__init__(self,vfile=vfile) + self.map('/mute',self.mute) + self.map('/pause',self.pause) + self.map('/atrack',self.audioswitch) + self.map('/endplay',self.endplay) + self.map('/replay',self.replay) + self.map('/setvalume',self.setVolume) + self.map('/setposition',self.setPosition) + self.map('/next',self.next) + + self.server.serve_forever() + self.fullscreen = True + label = Label(text='%s %d' % (self.ip,self.port), font_size=CSize(2)) + label.size = self.width - label.width, 0 + self.add_widget(label) + + def next(self,obj): + self.source = self.vfile + '?t=%f' % time.time() + + def map(self,p,f): + self.dispatcher.map(p,f,None) + +class VPlayer(BaseVPlayer): + def __init__(self,vfile=None, + playlist=None, + loop=False + ): + super().__init__(vfile=vfile) + self.loop = loop + self.menubar = None + self._popup = None + self.menu_status = False + self.manualMode = False + self.update_task = None + self.pb = None + if vfile: + if type(vfile) == type([]): + self.playlist = vfile + else: + self.playlist = [vfile] + self.curplay = 0 + self.play() + else: + self.playlist = [] + self.curplay = -1 + self._video.bind(on_touch_down=self.show_hide_menu) + self.register_event_type('on_playend') + + def __del__(self): + print('********** delete VPlayer instance ********') + if self._video is None: + return + if self.update_task: + self.update_task.cancel() + self.update_task = None + self._video.state = 'pause' + Window.allow_screensaver = True + del self._video + self._video = None + + + def video_end(self,t,v): + self.curplay += 1 + if not self.loop and self.curplay >= len(self.playlist): + self.dispatch('on_playend') + return + self.curplay = self.curplay % len(self.playlist) + self._video.source = self.playlist[self.curplay] + self._video.state = 'play' + + def totime(self,dur): + h = dur / 3600 + m = dur % 3600 / 60 + s = dur % 60 + return '%02d:%02d:%02d' % (h,m,s) + + def createProgressBar(self): + if hasattr(self._video._video, '_ffplayer'): + self.ffplayer = self._video._video._ffplayer + + if self.pb is None: + self.pb = BoxLayout(orientation='horizontal', + size_hint = (0.99,None),height=CSize(1.4)) + self.curposition = Label(text='0',width=CSize(4), + size_hint_x=None) + self.curposition.align='right' + self.maxposition = Label(text=self.totime(self._video.duration), + width=CSize(4),size_hint_x=None) + self.maxposition.align = 'left' + self.slider = Slider(min=0, + max=self._video.duration, + value=0, + orientation='horizontal', + step=0.01) + self.slider.bind(on_touch_down=self.enterManualMode) + self.slider.bind(on_touch_up=self.endManualMode) + self.manual_mode=False + + self.pb.add_widget(self.curposition) + self.pb.add_widget(self.slider) + self.pb.add_widget(self.maxposition) + self.menubar.add_widget(self.pb) + self.update_task = Clock.schedule_interval(self.update_slider,1) + + def enterManualMode(self,obj,touch): + if not self.slider.collide_point(*touch.pos): + return + self.manualMode = True + + def endManualMode(self,obj,touch): + if not self.manualMode: + return + if self._video.duration < 0.0001: + return + self._video.seek(self.slider.value/self._video.duration) + self.manualMode = False + + def update_slider(self,t): + self.curposition.text = self.totime(self._video.position) + if not self.manualMode: + self.slider.value = self._video.position + self.slider.max = self._video.duration + self.maxposition.text = self.totime(self._video.duration) + + def beforeDestroy(self): + try: + del self + + except Exception as e: + print_exc() + return True + def show_hide_menu(self,obj,touch): if not self.collide_point(*touch.pos): print('not inside the player') @@ -265,14 +319,15 @@ class VPlayer(FloatLayout): print('doube_tap') return - if self.menubar: + if not self.menubar: + self.buildMenu() + return + + if self.menu_status: self.remove_widget(self.menubar) - self.menu_status = not self.menu_status - if self.menu_status: - self.add_widget(self.menubar) - else: - self.remove_widget(self.menubar) - return + else: + self.add_widget(self.menubar) + self.menu_status = not self.menu_status def buildMenu(self): self.menubar = BoxLayout(orientation='horizontal', @@ -291,10 +346,6 @@ class VPlayer(FloatLayout): ) self.btn_mute.bind(on_press=self.mute) self.menubar.add_widget(self.btn_mute) - if self.can_openfile: - btn_open = Button(text='open') - btn_open.bind(on_press=self.openfile) - self.menubar.add_widget(btn_open) btn_cut = PressableImage(source=blockImage('next.jpg'), size_hint=(None,None), size=CSize(3,3) @@ -326,123 +377,30 @@ class VPlayer(FloatLayout): step=0.07) slider.bind(on_touch_up=self.setVolume) self.menubar.add_widget(slider) - self.menubar.add_widget(self.pb) self.menubar.pos = CSize(0,0) + self.createProgressBar() self.add_widget(self.menubar) self.menu_status = True - def endplay(self,btn): - self._video.seek(1.0,precise=True) - - def replay(self,btn): - self._video.seek(0.0,precise=True) - - def hideMenu(self): - self._popup.dismiss() - self.remove_widget(self.menubar) - self.menubar = None - - def audioswitch(self,btn): - if self.ffplayer is not None: - self.ffplayer.request_channel('audio') - - def setVolume(self,obj,touh): - self._video.volume = obj.value + def setVolume(self,obj,v=None): + BaseVPlayer.setVolume(self,obj,obj.value) def mute(self,btn): - if self._video.volume > 0.001: - self.old_volume = self._video.volume - self._video.volume = 0.0 - if self.menubar: + BaseVPlayer.mute(self,btn) + if self.menubar: + if self._video.volume < 0.001: btn.source = blockImage('volume.jpg') - else: - self._video.volume = self.old_volume - if self.menubar: + else: btn.source = blockImage('mute.jpg') - def stop(self): - try: - self._video.state = 'stop' - except: - print_exc() - - def on_disabled(self,o,v): - if self.disabled: - self.stop() - del self._video - def pause(self,t=None): - if self._video.state == 'play': - self._video.state = 'pause' - if self.menubar: + BaseVPlayer.pause(self,t) + if self.menubar: + if self._video.state == 'pause': self.btn_pause.source = blockImage('play.jpg') - else: - self._video.state = 'play' - if self.menubar: + else: self.btn_pause.source = blockImage('pause.jpg') - def openfile(self,t): - if self._popup is None: - def vfilter(path,filename): - vexts = ['.avi', - '.mpg', - '.mpe', - '.mpeg', - '.mlv', - '.dat', - '.mp4', - '.flv', - '.mov', - '.rm', - '.mkv', - '.rmvb', - '.asf', - '.3gp' - ] - for ext in vexts: - if filename.endswith(ext): - return True - return False - c = BoxLayout(orientation='vertical') - self.file_chooser = FileChooserListView() - self.file_chooser.filters = [vfilter] - self.file_chooser.multiselect = True - self.file_chooser.path = self.old_path - self.file_chooser.bind(on_submit=self.loadFilepath) - c.add_widget(self.file_chooser) - b = BoxLayout(size_hint_y=None,height=35) - c.add_widget(b) - cancel = Button(text='Cancel') - cancel.bind(on_press=self.cancelopen) - load = Button(text='load') - load.bind(on_press=self.playfile) - b.add_widget(load) - b.add_widget(cancel) - self._popup = Popup(title='Open file',content=c,size_hint=(0.9,0.9)) - self._popup.open() - - def cancelopen(self,obj): - self.hideMenu() - - def loadFilepath(self,obj,fpaths,evt): - print('fp=',fpaths,type(fpaths),'evt=',evt) - self.hideMenu() - self.playlist = fpaths - self.curplay = 0 - self._video.source = self.playlist[self.curplay] - self._video.state = 'play' - - def playfile(self,obj): - print('obj') - self.hideMenu() - self.playlist = [] - for f in self.file_chooser.selection: - fp = os.path.join(self.file_chooser.path,f) - self.playlist.append(fp) - self.curplay = 0 - self._video.source = self.playlist[self.curplay] - self._video.state = 'play' - if __name__ == '__main__': class MyApp(App): def build(self):