def startPlaying(self, mov=None, opts=None):
        """ Start playing the animation - start a "game" style loop, make FPS
            by drawing if current time == greater than a delta plus the time
            the last frame was drawn.
        """
        tempMovie = None
        tempOpts = None
        if mov != None: # playing a movie other than the main or help file
            tempMovie = self.mov
            self.mov = mov
        if opts != None:
            tempOpts = self.opts
            self.opts = opts

        self.commandMode = False
        oldTopLine = self.appState.topLine
        oldFirstCol = self.appState.firstCol
        cursorMode = self.appState.cursorMode
        if not self.statusBar.toolButton.hidden:
            self.statusBar.toolButton.draw()
            self.statusBar.toolButton.hide()
        if not self.statusBar.animButton.hidden:
            self.statusBar.animButton.draw()
            self.statusBar.animButton.hide()
        self.drawStatusBar()
        self.stdscr.nodelay(1) # do not wait for input when calling getch
        last_time = time.time()
        #self.statusBar.drawCharPickerButton.hide()
        if self.appState.playOnlyMode:
            self.statusBar.colorPicker.hide()
            #self.statusBar.colorPicker_bg_16.hide()
            self.appState.sideBarShowing = False
            self.statusBar.hide()
            self.cursorOff()
            self.setWindowTitle(self.appState.curOpenFileName)
        self.playing = True
        self.metaKey = 0
        if self.appState.playOnlyMode: 
            self.appState.drawBorders = False
        if not self.appState.playOnlyMode:
            # mode, show extra stuff.
            self.drawStatusBar()
        playedTimes = 1
        new_time = time.time()
        # see how many milliseconds we have to sleep for
        # then divide by 1000.0 since time.sleep() uses seconds
        self.appState.sleep_time = (1000.0 / self.opts.framerate) / 1000.0
        self.mov.gotoFrame(self.appState.playbackRange[0])
        mouseX, mouseY = 0, 0
        while self.playing:
            # catch keyboard input - to change framerate or stop pnlaying animation
            # get keyboard input, returns -1 if none available
            self.move(self.xy[0], self.xy[1])
            # Here refreshScreen=False because we will self.stdscr.refresh() below, after drawing the status bar (to avoid flicker)
            self.refresh(refreshScreen=False)
            if self.appState.viewModeShowInfo: 
                self.showFileInformation()
            if not self.appState.playOnlyMode:
                self.drawStatusBar()
                self.move(self.xy[0], self.xy[1] - 1)   # reposition cursor
            c = self.stdscr.getch()

            # handle resize
            resized = False
            realmaxY,realmaxX = self.realstdscr.getmaxyx()
            if self.appState.realmaxY != realmaxY:
                resized = True
                self.appState.realmaxY = realmaxY
            if self.appState.realmaxX != realmaxX:
                resized = True
                self.appState.realmaxX = realmaxX
            if resized:
                pass
                #self.notify("Debug: resized")
                #self.appState.topLine = 0
                #self.appState.firstCol = 0

            #debugstring = f"self.appState.realmaxY: {self.appState.realmaxY}, self.appState.realmaxX: {self.appState.realmaxX}, topLine: {self.appState.topLine}, firstCol: {self.appState.firstCol}"
            #self.addstr(self.statusBarLineNum - 1, 0, debugstring)
            self.stdscr.refresh()

            if c == 27:
                if self.appState.durview_running and self.appState.playOnlyMode:
                    # This block captures Esc before the "UI for Play Only mode"
                    # part below, so I'm putting this here.
                    self.playing = False
                    self.appState.topLine = 0
                    return True
                self.metaKey = 1
                self.pressingButton = False
                if self.pushingToClip:
                    self.pushingToClip = False
                self.disableMouseReporting()
                self.commandMode = True
                c = self.stdscr.getch() # normal esc

                # Clear out any canvas state as needed for command mode. For example...
                # If we think the mouse button is pressed.. stop thinking that.
                # In other words, un-stick the mouse button in case it's stuck:
            if self.metaKey == 1 and not self.appState.playOnlyMode and c != curses.ERR:   # esc
                self.pressingButton = False
                #if cursorMode != "Draw" and cursorMode != "Paint":
                #    print('\033[?1003l') # disable mouse reporting
                #    self.hardRefresh()
                #    curses.mousemask(1)
                #    curses.mousemask(curses.REPORT_MOUSE_POSITION | curses.ALL_MOUSE_EVENTS)
                if self.pushingToClip:
                    self.pushingToClip = False
                if c == 91: c = self.stdscr.getch() # alt-arrow does this in this mrxvt 5.x build
                if c in [61, 43]: # esc-= and esc-+ - fps up
                    self.increaseFPS()
                    self.appState.sleep_time = (1000.0 / self.opts.framerate) / 1000.0
                elif c in [45]: # esc-- (alt minus) - fps down
                    self.decreaseFPS()
                    self.appState.sleep_time = (1000.0 / self.opts.framerate) / 1000.0
                elif c in [98, curses.KEY_LEFT]:      # alt-left - prev bg color (in 16)
                    if self.appState.colorMode == "16":
                        self.prevBgColor()
                    elif self.appState.colorMode == "256":
                        self.prevFgColor()
                        #self.statusBar.colorPicker.handler.move_down_256()
                    c = None 
                elif c in [102, curses.KEY_RIGHT]:     # alt-right - next bg color
                    if self.appState.colorMode == "16":
                        self.nextBgColor()
                    elif self.appState.colorMode == "256":
                        self.nextFgColor()
                        #self.statusBar.colorPicker.handler.move_down_256()
                    c = None 
                elif c in [curses.KEY_DOWN, "\x1b\x1b\x5b\x42"]:      # alt-down - prev fg color
                    if self.appState.colorMode == "16":
                        self.prevFgColor()
                    elif self.appState.colorMode == "256":
                        self.statusBar.colorPicker.handler.move_down_256()
                    c = None 
                elif c == curses.KEY_UP:     # alt-up - next fg color
                    if self.appState.colorMode == "16":
                        self.nextFgColor()
                    elif self.appState.colorMode == "256":
                        self.statusBar.colorPicker.handler.move_up_256()
                    c = None 
                elif c == 91 or c == 339:   # alt-[ previous character set. apparently this doesn't work
                    self.prevCharSet() # during playback, c == -1, so 339 is alt-pgup, as a backup
                elif c == 93 or c == 338:   # alt-] (93) or aplt-pagdown next character set
                    self.nextCharSet()
                elif c == 83:       # alt-S - pick a character set
                    self.showCharSetPicker()
                    self.stdscr.nodelay(1) # do not wait for input when calling getch
                elif c == 46:       # alt-. - insert column
                    self.addCol(frange=self.appState.playbackRange)
                elif c == 44:      # alt-, - erase/pop current column
                    self.delCol(frange=self.appState.playbackRange)
                elif c == 39:      # alt-' - insert line
                    self.addLine(frange=self.appState.playbackRange)
                elif c == 59:        # alt-; - erase line
                    self.delLine(frange=self.appState.playbackRange)
                elif c == 105:      # alt-i - File/Canvas Information
                    self.clickedInfoButton()
                elif c == 109 or c == 102:    # alt-m or alt-f - load menu
                    #self.statusBar.menuButton.on_click() 
                    self.commandMode = False
                    self.openMenu("File")
                elif c == 99:     # alt-c - color picker
                    self.commandMode = False
                    #if self.appState.colorMode == "256":
                    #    self.statusBar.colorPickerButton.on_click()
                    #self.statusBar.colorPickerButton.on_click()
                    self.selectColorPicker()
                elif c == ord(' '):     # alt-space - insert drawing character
                    drawChar = self.appState.drawChar
                    x_param = self.xy[1]
                    y_param = self.xy[0]
                    self.insertChar(ord(drawChar), fg=self.colorfg, bg=self.colorbg, x=x_param, y=y_param, moveCursor=True, pushUndo=True, frange=self.appState.playbackRange)
                elif c == 122:  # alt-z = undo
                    self.clickedUndo()
                elif c == 114:  # alt-r = redo
                    self.clickedRedo()
                elif c == 82:   # alt-R = set playback range
                    self.getPlaybackRange()
                elif c in [112]: # alt-p - stop playing
                    self.stopPlaying()
                elif c == 111:                # alt-o - open
                    self.stopPlaying()
                    self.openFromMenu()     # as if we clicked menu->open
                elif c in [104, 63]:                # alt-h - help
                    self.showHelp()
                    c = None
                elif c == 113:                 # alt-q - quit
                    if self.appState.durview_running:
                        self.stopPlaying()
                        self.appState.editorRunning = False
                        return
                    else:
                        self.safeQuit()
                        self.stdscr.nodelay(1)
                        c = None
                elif c in [ord('1')]:    # esc-1 copy of F1 - insert extended character
                    self.insertChar(self.chMap['f1'], fg=self.colorfg, bg=self.colorbg,
                            frange=self.appState.playbackRange)
                    c = None
                    self.hardRefresh()
                elif c in [ord('2')]:    # esc-2 copy of F2 - insert extended character
                    self.insertChar(self.chMap['f2'], fg=self.colorfg, bg=self.colorbg,
                            frange=self.appState.playbackRange)
                    c = None
                    self.hardRefresh()
                elif c in [ord('3')]:    # F3 - insert extended character
                    self.insertChar(self.chMap['f3'], fg=self.colorfg, bg=self.colorbg,
                            frange=self.appState.playbackRange)
                    c = None
                    self.hardRefresh()
                elif c in [ord('4')]:    # F4 - insert extended character
                    self.insertChar(self.chMap['f4'], fg=self.colorfg, bg=self.colorbg,
                            frange=self.appState.playbackRange)
                    c = None
                    self.hardRefresh()
                elif c in [ord('5')]:    # F5 - insert extended character
                    self.insertChar(self.chMap['f5'], fg=self.colorfg, bg=self.colorbg,
                            frange=self.appState.playbackRange)
                    c = None
                    self.hardRefresh()
                elif c in [ord('6')]:    # F6 - insert extended character
                    self.insertChar(self.chMap['f6'], fg=self.colorfg, bg=self.colorbg,
                            frange=self.appState.playbackRange)
                    c = None
                    self.hardRefresh()
                elif c in [ord('7')]:    # F7 - insert extended character
                    self.insertChar(self.chMap['f7'], fg=self.colorfg, bg=self.colorbg,
                            frange=self.appState.playbackRange)
                    c = None
                    self.hardRefresh()
                elif c in [ord('8')]:    # F8 - insert extended character
                    self.insertChar(self.chMap['f8'], fg=self.colorfg, bg=self.colorbg,
                            frange=self.appState.playbackRange)
                    c = None
                    self.hardRefresh()
                elif c in [ord('9')]:    # F9 - insert extended character
                    self.insertChar(self.chMap['f9'], fg=self.colorfg, bg=self.colorbg,
                            frange=self.appState.playbackRange)
                    c = None
                    self.hardRefresh()
                elif c in [ord('0')]:    # F10 - insert extended character
                    self.insertChar(self.chMap['f10'], fg=self.colorfg, bg=self.colorbg,
                            frange=self.appState.playbackRange)
                    c = None
                    self.hardRefresh()
                else:
                    if self.appState.debug:
                        self.notify("keystroke: %d" % c) # alt-unknown
                self.commandMode = 0
                self.metaKey = 0
                c = None 
            elif c != -1:   # -1 means no keys are pressed.
                # up or down to change framerate, otherwise stop playing
                #if self.appState.durview_running:   # Extra UI for DurView
                #    if c in [ord('e')]:     # e - open file in editor
                #        self.openEditorFromDurview()
                #        c = None
                if self.appState.playOnlyMode:  # UI for Play-only mode
                    self.handlePlayOnlyModeInput(c)
                else:
                    if c == curses.KEY_MOUSE: # Remember, we are playing here
                        try:
                            _, mouseX, mouseY, _, mouseState = curses.getmouse()
                        except:
                            pass
                        realmaxY,realmaxX = self.realstdscr.getmaxyx()
                        # enable mouse tracking only when the button is pressed
                        if mouseState == curses.BUTTON1_CLICKED:
                            if self.pressingButton:
                                self.pressingButton = False
                                if cursorMode != "Draw" and cursorMode != "Paint":
                                    print('\033[?1003l') # disable mouse reporting
                                    self.hardRefresh()
                                    curses.mousemask(1)
                                    curses.mousemask(curses.REPORT_MOUSE_POSITION | curses.ALL_MOUSE_EVENTS)
                                if self.pushingToClip:
                                    self.pushingToClip = False

                        if not self.appState.hasMouseScroll:
                            curses.BUTTON5_PRESSED = 0
                            curses.BUTTON4_PRESSED = 0
                        try:
                            curses.BUTTON5_PRESSED
                        except:
                            curses.BUTTON5_PRESSED = 0
                        try:
                            curses.BUTTON4_PRESSED
                        except:
                            curses.BUTTON4_PRESSED = 0
                        if mouseState & curses.BUTTON4_PRESSED:   # wheel up
                            self.move_cursor_up()
                        elif mouseState & curses.BUTTON5_PRESSED:   # wheel down
                            self.move_cursor_down()
                            
                        if mouseState & curses.BUTTON1_PRESSED:
                            #if mouseY < self.mov.sizeY and mouseX < self.mov.sizeX: # in edit area
                            if mouseY < self.mov.sizeY and mouseX < self.mov.sizeX \
                                and mouseY + self.appState.topLine < self.appState.topLine + self.statusBarLineNum:
                                if not self.pressingButton:
                                    self.pressingButton = True
                                    print('\033[?1003h') # enable mouse tracking with the XTERM APIP
                                    self.hardRefresh()
                            else:
                                self.pressingButton = False
                                if cursorMode != "Draw" and cursorMode != "Paint":
                                    print('\033[?1003l') # disable mouse reporting
                                    self.hardRefresh()
                                    curses.mousemask(1)
                                    curses.mousemask(curses.REPORT_MOUSE_POSITION | curses.ALL_MOUSE_EVENTS)
                                if self.pushingToClip:
                                    self.pushingToClip = False
                        else:
                            if self.pressingButton:
                                self.pressingButton = False
                                if self.pushingToClip:
                                    self.pushingToClip = False
                                #if self.appState.cursorMode != "Draw":
                                print('\033[?1003l') # disable mouse reporting
                                self.hardRefresh()
                                curses.mousemask(1)
                                curses.mousemask(curses.REPORT_MOUSE_POSITION | curses.ALL_MOUSE_EVENTS)
                        if self.pressingButton or mouseState == curses.BUTTON1_CLICKED:    # self.playing == True
                            self.gui.got_click("Click", mouseX, mouseY)
                            if mouseY < self.mov.sizeY and mouseX < self.mov.sizeX \
                                and mouseY + self.appState.topLine < self.appState.topLine + self.statusBarLineNum:
                                # we clicked in edit area, so move the cursor
                                self.xy[1] = mouseX + 1 # set cursor position
                                self.xy[0] = mouseY + self.appState.topLine
                            elif mouseX < realmaxX and mouseY in [self.statusBarLineNum, self.statusBarLineNum+1]:   # we clicked on the status bar while playing.
                                if mouseY == self.statusBarLineNum: # clicked upper bar
                                    offset = 6  # making room for the menu bar
                                    tOffset = realmaxX - (realmaxX - self.transportOffset) + 6
                                    if not self.appState.narrowWindow:
                                        if mouseX in [tOffset, tOffset + 1]:  # clicked pause button
                                            self.clickHighlight(tOffset, "||")
                                            self.stopPlaying()
                                        elif mouseX == 12 + offset:    # clicked FPS down
                                            self.clickHighlight(12 + offset, "<")
                                            self.decreaseFPS()
                                            self.appState.sleep_time = (1000.0 / self.opts.framerate) / 1000.0
                                        elif mouseX == 16 + offset:    # clicked FPS up
                                            self.clickHighlight(16 + offset, ">")
                                            self.increaseFPS()
                                            self.appState.sleep_time = (1000.0 / self.opts.framerate) / 1000.0
                                elif mouseY == self.statusBarLineNum+1:    # clicked bottom bar
                                    if not self.appState.narrowWindow:
                                        if mouseX in range(4,20): 
                                            fg = mouseX - 3  
                                            self.setFgColor(fg)
                                        elif mouseX in range(25,33):    
                                            bg = mouseX - 24
                                            self.setBgColor(bg)
                                        elif mouseX == self.chMap_offset + len(self.chMapString):  # clicked next character set
                                            self.clickHighlight(self.chMap_offset + len(self.chMapString), ">", bar='bottom')
                                            self.nextCharSet()
                                        elif mouseX == self.chMap_offset - 1:  # clicked previous character set
                                            self.clickHighlight(self.chMap_offset - 1, "<", bar='bottom')
                                            self.prevCharSet()
                                    if self.appState.debug:
                                        self.notify("bottom bar. " + str([mouseX, mouseY]))
                                elif self.appState.debug:
                                    self.notify("clicked. " + str([mouseX, mouseY]))

                    elif c == curses.KEY_LEFT:      # left - move cursor right a character
                        self.move_cursor_left()
                    elif c == curses.KEY_RIGHT:     # right - move cursor right
                        self.move_cursor_right()
                    elif c == curses.KEY_UP:    # up - move cursor up
                        self.move_cursor_up()
                    elif c == curses.KEY_DOWN:  # down - move curosr down
                        self.move_cursor_down()
                    elif c in [339, curses.KEY_PPAGE]:  # page up
                        self.move_cursor_pgup()
                    elif c in [338, curses.KEY_NPAGE]:  # page down
                        self.move_cursor_pgdown()
                    elif c in [339, curses.KEY_HOME]:  # 339 = home
                        self.xy[1] = 1
                    elif c in [338, curses.KEY_END]:   # 338 = end
                        self.xy[1] = self.mov.sizeX
                    elif c in [10, 13, curses.KEY_ENTER]:               # enter (10 if we
                        self.move_cursor_enter()
                        # don't do curses.nonl())
                    elif c in [263, 127]:              # backspace
                        self.backspace()
                    elif c in [9, 353]:     # 9 = tab, 353 = shift-tab
                        #if self.appState.colorMode == "256":
                        #    self.statusBar.colorPickerButton.on_click()
                        self.statusBar.colorPickerButton.on_click()
                    elif c in [330]:              # delete
                        self.deleteKeyPop(frange=self.appState.playbackRange)
                    elif c in [383]:              # shift-delete - delete from opposite direction
                        self.reverseDelete(frange=self.appState.playbackRange)
                    elif c in [1, curses.KEY_HOME]:     # ctrl-a or home
                        self.xy[1] = 1
                    elif c in [5, curses.KEY_END]:      # ctrl-e or end
                        self.xy[1] = self.mov.sizeX
                    elif c in [curses.KEY_F1]:    # F1 - insert extended character
                        self.insertChar(self.chMap['f1'], fg=self.colorfg, bg=self.colorbg,
                                frange=self.appState.playbackRange)
                        c = None
                    elif c in [curses.KEY_F2]:    # F2 - insert extended character
                        self.insertChar(self.chMap['f2'], fg=self.colorfg, bg=self.colorbg,
                                frange=self.appState.playbackRange)
                        c = None
                    elif c in [curses.KEY_F3]:    # F3 - insert extended character
                        self.insertChar(self.chMap['f3'], fg=self.colorfg, bg=self.colorbg,
                                frange=self.appState.playbackRange)
                        c = None
                    elif c in [curses.KEY_F4]:    # F4 - insert extended character
                        self.insertChar(self.chMap['f4'], fg=self.colorfg, bg=self.colorbg,
                                frange=self.appState.playbackRange)
                        c = None
                    elif c in [curses.KEY_F5]:    # F5 - insert extended character
                        self.insertChar(self.chMap['f5'], fg=self.colorfg, bg=self.colorbg,
                                frange=self.appState.playbackRange)
                        c = None
                    elif c in [curses.KEY_F6]:    # F6 - insert extended character
                        self.insertChar(self.chMap['f6'], fg=self.colorfg, bg=self.colorbg,
                                frange=self.appState.playbackRange)
                        c = None
                    elif c in [curses.KEY_F7]:    # F7 - insert extended character
                        self.insertChar(self.chMap['f7'], fg=self.colorfg, bg=self.colorbg,
                                frange=self.appState.playbackRange)
                        c = None
                    elif c in [curses.KEY_F8]:    # F8 - insert extended character
                        self.insertChar(self.chMap['f8'], fg=self.colorfg, bg=self.colorbg,
                                frange=self.appState.playbackRange)
                        c = None
                    elif c in [curses.KEY_F9]:    # F9 - insert extended character
                        self.insertChar(self.chMap['f9'], fg=self.colorfg, bg=self.colorbg,
                                frange=self.appState.playbackRange)
                        c = None
                    elif c in [curses.KEY_F10]:    # F10 - insert extended character
                        self.insertChar(self.chMap['f10'], fg=self.colorfg, bg=self.colorbg,
                                frange=self.appState.playbackRange)
                        c = None
                    elif c != None and c <= 128 and c >= 32:      # normal printable character
                        self.insertChar(c, fg=self.colorfg, bg=self.colorbg, frange=self.appState.playbackRange)

            shouldDraw = False
            while True:
                if self.appState.playOnlyMode:
                    self.handlePlayOnlyModeInput(self.stdscr.getch())

                new_time = time.time()
                frame_delay = self.mov.currentFrame.delay
                if frame_delay > 0:
                    realDelayTime = frame_delay
                else:
                    realDelayTime = self.appState.sleep_time  # self.appState.sleep_time == (1000.0 / self.opts.framerate) / 1000.0
                time.sleep(0.009) # to keep from sucking up cpu

                if new_time >= (last_time + realDelayTime): # Time to update the frame? If so...
                    shouldDraw = True
                    break
                if not self.appState.playOnlyMode:
                    break
                if not self.playing:
                    break

            if shouldDraw:
                last_time = new_time
                # draw animation
                if not self.appState.playOnlyMode:
                    if self.mov.currentFrameNumber == self.appState.playbackRange[1]:
                        self.mov.gotoFrame(self.appState.playbackRange[0])
                    else:
                        self.mov.nextFrame()
                else:
                    self.mov.nextFrame()
                if not self.appState.playOnlyMode: # if we're not in play-only
                    # mode, show extra stuff.
                    self.drawStatusBar()
                else:
                    if self.appState.playNumberOfTimes > 0:   # if we're playing x times
                        if self.mov.currentFrameNumber == self.mov.frameCount:
                            # and on the last frame
                            if playedTimes < self.appState.playNumberOfTimes:
                                playedTimes += 1
                            else:   # we've played the desired number of times.
                                self.playing = False


        if tempMovie != None:   # Need to switch back to main movie
            self.mov = tempMovie
        if tempOpts != None:
            self.opts = tempOpts

        self.appState.topLine = oldTopLine
        self.appState.firstCol = oldFirstCol
        self.stdscr.nodelay(0) # back to wait for input when calling getch
        self.cursorOn()
 Start playing the animation - start a "game" style loop, make FPS
            by drawing if current time == greater than a delta plus the time
            the last frame was drawn.
        
durdraw/durdraw_ui_curses.py