Exemplo n.º 1
0
    def __init__(self, cacheFolder, progressBar, progressText):
        super(MainWindow, self).__init__()
        self.setWindowTitle('DD监控室')
        self.resize(1600, 900)
        self.maximumToken = True
        self.cacheFolder = cacheFolder
        self.configJSONPath = os.path.join(application_path,
                                           r'utils/config.json')
        self.config = {}
        if os.path.exists(self.configJSONPath):  # 读取config
            if os.path.getsize(self.configJSONPath):
                try:
                    with codecs.open(self.configJSONPath, 'r', 'utf8') as f:
                        self.config = json.loads(f.read())
                    # self.config = json.loads(open(self.configJSONPath).read())
                except Exception as e:
                    print(str(e))
                    self.config = {}
        if not self.config:  # 读取config失败 尝试读取备份
            for backupNumber in [1, 2, 3]:  # 备份预设123
                self.configJSONPath = os.path.join(
                    application_path, r'utils/config_备份%d.json' % backupNumber)
                if os.path.exists(self.configJSONPath):  # 如果备份文件存在
                    if os.path.getsize(self.configJSONPath):  # 如过备份文件有效
                        try:
                            self.config = json.loads(
                                open(self.configJSONPath).read())
                            break
                        except:
                            self.config = {}
        if self.config:  # 如果能成功读取到config文件
            while len(self.config['player']) < 9:
                self.config['player'].append(0)
            if type(self.config['roomid']) == list:
                roomIDList = self.config['roomid']
                self.config['roomid'] = {}
                for roomID in roomIDList:
                    self.config['roomid'][roomID] = False
            if 'quality' not in self.config:
                self.config['quality'] = [80] * 9
            if 'audioChannel' not in self.config:
                self.config['audioChannel'] = [0] * 9
            if 'translator' not in self.config:
                self.config['translator'] = [True] * 9
            for index, textSetting in enumerate(self.config['danmu']):
                if type(textSetting) == bool:
                    self.config['danmu'][index] = [
                        textSetting, 20, 1, 7, 0, '【 [ {'
                    ]
            if 'hardwareDecode' not in self.config:
                self.config['hardwareDecode'] = True
            if 'maxCacheSize' not in self.config:
                self.config['maxCacheSize'] = 2048000
                logging.warn('最大缓存没有被设置,使用默认1G')
            if 'startWithDanmu' not in self.config:
                self.config['startWithDanmu'] = True
                logging.warn('启动时加载弹幕没有被设置,默认加载')
        else:
            self.config = {
                'roomid': {
                    '21396545': False,
                    '21402309': False,
                    '22384516': False,
                    '8792912': False,
                    '21696950': False,
                    '14327465': False,
                    '704808': True,
                    '1321846': False,
                    '56237': False,
                    '8725120': False,
                    '22347054': False,
                    '14052636': False,
                    '21320551': False,
                    '876396': False,
                    '21448649': False,
                    '24393': False,
                    '12845193': False,
                    '41682': False,
                    '21652717': False,
                    '22571958': False,
                    '21919321': True,
                    '21013446': False
                },  # 置顶显示
                'layout': [(0, 0, 1, 1), (0, 1, 1, 1), (1, 0, 1, 1),
                           (1, 1, 1, 1)],
                # 'player': ['21396545', '21402309', '22384516', '8792912',
                #            '21696950', '14327465', '704808', '1321846', '318'],
                'player': ['0'] * 9,
                'quality': [80] * 9,
                'audioChannel': [0] * 9,
                'muted': [1] * 9,
                'volume': [50] * 9,
                'danmu': [[True, 50, 1, 7, 0, '【 [ {']] *
                9,  # 显示弹幕, 透明度, 横向占比, 纵向占比, 显示同传, 同传过滤字符
                'globalVolume': 30,
                'control': True,
                'hardwareDecode': True,
                'maxCacheSize': 2048000,
                'startWithDanmu': True
            }
        self.dumpConfig = DumpConfig(self.config)
        mainWidget = QWidget()
        self.setCentralWidget(mainWidget)
        self.mainLayout = QGridLayout(mainWidget)
        self.mainLayout.setSpacing(0)
        self.mainLayout.setContentsMargins(0, 0, 0, 0)
        self.layoutSettingPanel = LayoutSettingPanel()
        self.layoutSettingPanel.layoutConfig.connect(self.changeLayout)
        self.version = Version()
        self.hotKey = HotKey()
        self.pay = pay()

        self.videoWidgetList = []
        self.popVideoWidgetList = []
        vlcProgressCounter = 1
        for i in range(9):
            volume = self.config['volume'][i]
            progressText.setText('设置第%s个主层播放器...' % str(i + 1))
            self.videoWidgetList.append(
                VideoWidget(i,
                            volume,
                            cacheFolder,
                            textSetting=self.config['danmu'][i],
                            maxCacheSize=self.config['maxCacheSize'],
                            startWithDanmu=self.config['startWithDanmu']))
            vlcProgressCounter += 1
            progressBar.setValue(vlcProgressCounter)
            # self.videoWidgetList[i].mutedChanged.connect(self.mutedChanged)  # 硬盘io过高 屏蔽掉 退出的时候统一保存
            # self.videoWidgetList[i].volumeChanged.connect(self.volumeChanged)  # 硬盘io过高 屏蔽掉 退出的时候统一保存
            self.videoWidgetList[i].addMedia.connect(self.addMedia)
            self.videoWidgetList[i].deleteMedia.connect(self.deleteMedia)
            self.videoWidgetList[i].exchangeMedia.connect(self.exchangeMedia)
            # self.videoWidgetList[i].setDanmu.connect(self.setDanmu)  # 硬盘io过高 屏蔽掉 退出的时候统一保存
            # self.videoWidgetList[i].setTranslator.connect(self.setTranslator)  # 已废弃
            self.videoWidgetList[i].changeQuality.connect(self.setQuality)
            # self.videoWidgetList[i].changeAudioChannel.connect(self.setAudioChannel)  # 硬盘io过高 屏蔽掉 退出的时候统一保存
            self.videoWidgetList[i].popWindow.connect(self.popWindow)
            self.videoWidgetList[i].hideBarKey.connect(self.openControlPanel)
            self.videoWidgetList[i].fullScreenKey.connect(self.fullScreen)
            self.videoWidgetList[i].muteExceptKey.connect(self.muteExcept)
            self.videoWidgetList[i].mediaMute(self.config['muted'][i],
                                              emit=False)
            self.videoWidgetList[i].slider.setValue(self.config['volume'][i])
            self.videoWidgetList[i].quality = self.config['quality'][i]
            self.videoWidgetList[i].audioChannel = self.config['audioChannel'][
                i]
            self.popVideoWidgetList.append(
                VideoWidget(i + 9,
                            volume,
                            cacheFolder,
                            True,
                            '悬浮窗', [1280, 720],
                            maxCacheSize=self.config['maxCacheSize'],
                            startWithDanmu=self.config['startWithDanmu']))
            vlcProgressCounter += 1
            progressBar.setValue(vlcProgressCounter)
            progressText.setText('设置第%s个悬浮窗播放器...' % str(i + 1))
            app.processEvents()
            logging.info("VLC设置完毕 %s / 9" % str(i + 1))
        self.setPlayer()

        self.controlBar = QToolBar()
        self.addToolBar(self.controlBar)
        self.controlBar.show(
        ) if self.config['control'] else self.controlBar.hide()

        self.globalPlayToken = True
        self.play = PushButton(self.style().standardIcon(QStyle.SP_MediaPause))
        self.play.clicked.connect(self.globalMediaPlay)
        self.controlBar.addWidget(self.play)
        self.reload = PushButton(self.style().standardIcon(
            QStyle.SP_BrowserReload))
        self.reload.clicked.connect(self.globalMediaReload)
        self.controlBar.addWidget(self.reload)
        self.globalMuteToken = False
        self.volumeButton = PushButton(self.style().standardIcon(
            QStyle.SP_MediaVolume))
        self.volumeButton.clicked.connect(self.globalMediaMute)
        self.controlBar.addWidget(self.volumeButton)
        self.slider = Slider()
        self.slider.setValue(self.config['globalVolume'])
        self.slider.value.connect(self.globalSetVolume)
        self.controlBar.addWidget(self.slider)
        self.stop = PushButton(self.style().standardIcon(
            QStyle.SP_DialogCancelButton))
        self.stop.clicked.connect(self.globalMediaStop)
        self.controlBar.addWidget(self.stop)
        progressText.setText('设置播放器控制...')

        self.addButton = QPushButton('+')
        self.addButton.setFixedSize(160, 104)
        self.addButton.setStyleSheet('border:3px dotted #EEEEEE')
        self.addButton.setFont(QFont('Arial', 24, QFont.Bold))
        progressText.setText('设置添加控制...')

        self.controlBar.addWidget(self.addButton)
        self.controlBar.addWidget(QLabel())
        progressText.setText('设置全局控制...')

        self.scrollArea = ScrollArea()
        self.scrollArea.setStyleSheet('border-width:0px')
        self.scrollArea.setMinimumHeight(111)
        self.controlBar.addWidget(self.scrollArea)
        self.liverPanel = LiverPanel(self.config['roomid'])
        self.liverPanel.addLiverRoomWidget.getHotLiver.start()
        self.liverPanel.addToWindow.connect(self.addCoverToPlayer)
        self.liverPanel.dumpConfig.connect(self.dumpConfig.start)  # 保存config
        self.liverPanel.refreshIDList.connect(
            self.refreshPlayerStatus)  # 刷新播放器
        self.scrollArea.setWidget(self.liverPanel)
        self.addButton.clicked.connect(self.liverPanel.openLiverRoomPanel)
        progressText.setText('设置主播选择控制...')

        self.optionMenu = self.menuBar().addMenu('设置')
        self.controlBarToken = self.config['control']
        layoutConfigAction = QAction('布局方式',
                                     self,
                                     triggered=self.openLayoutSetting)
        self.optionMenu.addAction(layoutConfigAction)
        globalQualityMenu = self.optionMenu.addMenu('全局画质 ►')
        originQualityAction = QAction(
            '原画', self, triggered=lambda: self.globalQuality(10000))
        globalQualityMenu.addAction(originQualityAction)
        bluerayQualityAction = QAction(
            '蓝光', self, triggered=lambda: self.globalQuality(400))
        globalQualityMenu.addAction(bluerayQualityAction)
        highQualityAction = QAction('超清',
                                    self,
                                    triggered=lambda: self.globalQuality(250))
        globalQualityMenu.addAction(highQualityAction)
        lowQualityAction = QAction('流畅',
                                   self,
                                   triggered=lambda: self.globalQuality(80))
        globalQualityMenu.addAction(lowQualityAction)
        globalAudioMenu = self.optionMenu.addMenu('全局音效 ►')
        audioOriginAction = QAction(
            '原始音效', self, triggered=lambda: self.globalAudioChannel(0))
        globalAudioMenu.addAction(audioOriginAction)
        audioDolbysAction = QAction(
            '杜比音效', self, triggered=lambda: self.globalAudioChannel(5))
        globalAudioMenu.addAction(audioDolbysAction)
        hardDecodeMenu = self.optionMenu.addMenu('解码方案 ►')
        hardDecodeAction = QAction('硬解',
                                   self,
                                   triggered=lambda: self.setDecode(True))
        hardDecodeMenu.addAction(hardDecodeAction)
        softDecodeAction = QAction('软解',
                                   self,
                                   triggered=lambda: self.setDecode(False))
        hardDecodeMenu.addAction(softDecodeAction)
        cacheSizeSetting = QAction('最大缓存设置',
                                   self,
                                   triggered=self.openCacheSizeSetting)
        self.optionMenu.addAction(cacheSizeSetting)
        startWithDanmuSetting = QAction(
            '自动加载弹幕设置', self, triggered=self.openStartWithDanmuSetting)
        self.optionMenu.addAction(startWithDanmuSetting)
        controlPanelAction = QAction('显示 / 隐藏控制条(H)',
                                     self,
                                     triggered=self.openControlPanel)
        self.optionMenu.addAction(controlPanelAction)
        self.fullScreenAction = QAction('全屏(F) / 退出(Esc)',
                                        self,
                                        triggered=self.fullScreen)
        self.optionMenu.addAction(self.fullScreenAction)
        exportConfig = QAction('导出预设', self, triggered=self.exportConfig)
        self.optionMenu.addAction(exportConfig)
        importConfig = QAction('导入预设', self, triggered=self.importConfig)
        self.optionMenu.addAction(importConfig)
        progressText.setText('设置选项菜单...')

        self.versionMenu = self.menuBar().addMenu('帮助')
        bilibiliAction = QAction('B站视频', self, triggered=self.openBilibili)
        self.versionMenu.addAction(bilibiliAction)
        hotKeyAction = QAction('快捷键', self, triggered=self.openHotKey)
        self.versionMenu.addAction(hotKeyAction)
        versionAction = QAction('检查版本', self, triggered=self.openVersion)
        self.versionMenu.addAction(versionAction)
        otherDDMenu = self.versionMenu.addMenu('其他DD系列工具 ►')
        DDSubtitleAction = QAction('DD烤肉机',
                                   self,
                                   triggered=self.openDDSubtitle)
        otherDDMenu.addAction(DDSubtitleAction)
        DDThanksAction = QAction('DD答谢机', self, triggered=self.openDDThanks)
        otherDDMenu.addAction(DDThanksAction)
        progressText.setText('设置帮助菜单...')

        self.payMenu = self.menuBar().addMenu('开源和投喂')
        githubAction = QAction('GitHub', self, triggered=self.openGithub)
        self.payMenu.addAction(githubAction)
        feedAction = QAction('投喂作者', self, triggered=self.openFeed)
        self.payMenu.addAction(feedAction)
        # killAction = QAction('自尽(测试)', self, triggered=lambda a: 0 / 0)
        # self.payMenu.addAction(killAction)
        progressText.setText('设置关于菜单...')

        self.oldMousePos = QPoint(0, 0)  # 初始化鼠标坐标
        self.hideMouseCnt = 90
        self.mouseTrackTimer = QTimer()
        self.mouseTrackTimer.timeout.connect(self.checkMousePos)
        self.mouseTrackTimer.start(100)  # 0.1s检测一次
        progressText.setText('设置UI...')
        logging.info('UI构造完毕')
Exemplo n.º 2
0
    def __init__(self, cacheFolder, progressBar, progressText):
        super(MainWindow, self).__init__()
        self.setWindowTitle('DD监控室')
        self.resize(1600, 900)
        self.maximumToken = True
        self.soloToken = False  # 记录静音除鼠标悬停窗口以外的其他所有窗口的标志位 True就是恢复所有房间声音
        self.cacheFolder = cacheFolder

        # ---- json 配置文件加载 ----
        self.configJSONPath = os.path.join(application_path,
                                           r'utils/config.json')
        self.config = {}
        # 读取默认的 config
        if os.path.exists(self.configJSONPath):
            if os.path.getsize(self.configJSONPath):
                try:
                    with codecs.open(self.configJSONPath,
                                     'r',
                                     encoding='utf-8') as f:
                        self.config = json.loads(f.read())
                    # self.config = json.loads(open(self.configJSONPath).read())
                except:
                    logging.exception('json 配置读取失败')
                    self.config = {}
        # 读取config失败 尝试读取备份
        if not self.config:
            for backupNumber in [1, 2, 3]:  # 备份预设123
                self.configJSONPath = os.path.join(
                    application_path, r'utils/config_备份%d.json' % backupNumber)
                if os.path.exists(self.configJSONPath):  # 如果备份文件存在
                    if os.path.getsize(self.configJSONPath):  # 如过备份文件有效
                        try:
                            self.config = json.loads(
                                open(self.configJSONPath).read())
                            break
                        except:
                            logging.exception('json 备份配置读取失败')
                            self.config = {}
        # 如果能成功读取到config文件
        if self.config:
            while len(self.config['player']) < 9:
                self.config['player'].append(0)
            if type(self.config['roomid']) == list:
                roomIDList = self.config['roomid']
                self.config['roomid'] = {}
                for roomID in roomIDList:
                    self.config['roomid'][roomID] = False
            if 'quality' not in self.config:
                self.config['quality'] = [80] * 9
            if 'audioChannel' not in self.config:
                self.config['audioChannel'] = [0] * 9
            if 'translator' not in self.config:
                self.config['translator'] = [True] * 9
            for index, textSetting in enumerate(self.config['danmu']):
                if type(textSetting) == bool:
                    self.config['danmu'][index] = [
                        textSetting, 20, 1, 7, 0, '【 [ {'
                    ]
            if 'hardwareDecode' not in self.config:
                self.config['hardwareDecode'] = True
            if 'maxCacheSize' not in self.config:
                self.config['maxCacheSize'] = 2048000
                logging.warning('最大缓存没有被设置,使用默认1G')
            if 'startWithDanmu' not in self.config:
                self.config['startWithDanmu'] = True
                logging.warning('启动时加载弹幕没有被设置,默认加载')
            for danmuConfig in self.config['danmu']:
                if len(danmuConfig) == 6:
                    danmuConfig.append(10)
        else:  # 默认和备份 json 配置均读取失败
            self.config = {
                'roomid': {
                    '21396545': False,
                    '21402309': False,
                    '22384516': False,
                    '8792912': False
                },  # 置顶显示
                'layout': [(0, 0, 1, 1), (0, 1, 1, 1), (1, 0, 1, 1),
                           (1, 1, 1, 1)],
                'player': ['0'] * 9,
                'quality': [80] * 9,
                'audioChannel': [0] * 9,
                'muted': [1] * 9,
                'volume': [50] * 9,
                'danmu': [[True, 50, 1, 7, 0, '【 [ {', 10]] *
                9,  # 显示,透明,横向,纵向,类型,同传字符,字体大小
                'globalVolume': 30,
                'control': True,
                'hardwareDecode': True,
                'maxCacheSize': 2048000,
                'startWithDanmu': True
            }
        self.dumpConfig = DumpConfig(self.config)

        # ---- 主窗体控件 ----
        mainWidget = QWidget()
        self.setCentralWidget(mainWidget)
        # Grid 布局
        self.mainLayout = QGridLayout(mainWidget)
        self.mainLayout.setSpacing(0)
        self.mainLayout.setContentsMargins(0, 0, 0, 0)
        self.layoutSettingPanel = LayoutSettingPanel()
        self.layoutSettingPanel.layoutConfig.connect(self.changeLayout)
        self.version = Version()
        self.hotKey = HotKey()
        self.pay = pay()

        # ---- 内嵌/弹出播放器初始化 ----
        self.videoWidgetList = []
        self.popVideoWidgetList = []
        vlcProgressCounter = 1
        for i in range(9):
            volume = self.config['volume'][i]
            progressText.setText('设置第%s个主层播放器...' % str(i + 1))
            self.videoWidgetList.append(
                VideoWidget(i,
                            volume,
                            cacheFolder,
                            textSetting=self.config['danmu'][i],
                            maxCacheSize=self.config['maxCacheSize'],
                            startWithDanmu=self.config['startWithDanmu']))
            vlcProgressCounter += 1
            progressBar.setValue(vlcProgressCounter)
            self.videoWidgetList[i].mutedChanged.connect(self.mutedChanged)
            self.videoWidgetList[i].volumeChanged.connect(self.volumeChanged)
            self.videoWidgetList[i].addMedia.connect(self.addMedia)
            self.videoWidgetList[i].deleteMedia.connect(self.deleteMedia)
            self.videoWidgetList[i].exchangeMedia.connect(self.exchangeMedia)
            # self.videoWidgetList[i].setDanmu.connect(self.setDanmu)  # 硬盘io过高 屏蔽掉 退出的时候统一保存
            # self.videoWidgetList[i].setTranslator.connect(self.setTranslator)  # 已废弃
            self.videoWidgetList[i].changeQuality.connect(self.setQuality)
            # self.videoWidgetList[i].changeAudioChannel.connect(self.setAudioChannel)  # 硬盘io过高 屏蔽掉 退出的时候统一保存
            self.videoWidgetList[i].popWindow.connect(self.popWindow)
            self.videoWidgetList[i].hideBarKey.connect(self.openControlPanel)
            self.videoWidgetList[i].fullScreenKey.connect(self.fullScreen)
            self.videoWidgetList[i].muteExceptKey.connect(self.muteExcept)
            self.videoWidgetList[i].mediaMute(self.config['muted'][i],
                                              emit=False)
            self.videoWidgetList[i].slider.setValue(self.config['volume'][i])
            self.videoWidgetList[i].quality = self.config['quality'][i]
            self.videoWidgetList[i].audioChannel = self.config['audioChannel'][
                i]
            self.popVideoWidgetList.append(
                VideoWidget(i + 9,
                            volume,
                            cacheFolder,
                            True,
                            '悬浮窗', [1280, 720],
                            maxCacheSize=self.config['maxCacheSize'],
                            startWithDanmu=self.config['startWithDanmu']))
            self.popVideoWidgetList[i].closePopWindow.connect(
                self.closePopWindow)
            vlcProgressCounter += 1
            progressBar.setValue(vlcProgressCounter)
            progressText.setText('设置第%s个悬浮窗播放器...' % str(i + 1))
            app.processEvents()
            logging.info("VLC设置完毕 %s / 9" % str(i + 1))
        # 设置所有播放器布局
        self.setPlayer()

        self.controlDock = QDockWidget('控制条')
        self.controlDock.setFixedWidth(178)
        self.controlDock.setFloating(False)
        self.controlDock.setAllowedAreas(Qt.LeftDockWidgetArea
                                         | Qt.RightDockWidgetArea
                                         | Qt.TopDockWidgetArea)
        self.addDockWidget(Qt.TopDockWidgetArea, self.controlDock)
        controlWidget = ControlWidget()
        self.controlDock.setWidget(controlWidget)
        self.controlBarLayout = QGridLayout(controlWidget)
        self.globalPlayToken = True
        self.play = PushButton(self.style().standardIcon(QStyle.SP_MediaPause))
        self.play.clicked.connect(self.globalMediaPlay)
        self.controlBarLayout.addWidget(self.play, 0, 0, 1, 1)
        self.reload = PushButton(self.style().standardIcon(
            QStyle.SP_BrowserReload))
        self.reload.clicked.connect(self.globalMediaReload)
        self.controlBarLayout.addWidget(self.reload, 0, 1, 1, 1)
        self.stop = PushButton(self.style().standardIcon(
            QStyle.SP_DialogCancelButton))
        self.stop.clicked.connect(self.globalMediaStop)
        self.controlBarLayout.addWidget(self.stop, 0, 2, 1, 1)

        # 全局弹幕设置
        self.danmuOption = TextOpation()
        self.danmuOption.setWindowTitle('全局弹幕窗设置')
        self.danmuOption.opacitySlider.value.connect(
            self.setGlobalDanmuOpacity)
        self.danmuOption.horizontalCombobox.currentIndexChanged.connect(
            self.setGlobalHorizontalPercent)
        self.danmuOption.verticalCombobox.currentIndexChanged.connect(
            self.setGlobalVerticalPercent)
        self.danmuOption.translateCombobox.currentIndexChanged.connect(
            self.setGlobalTranslateBrowser)
        self.danmuOption.translateFitler.textChanged.connect(
            self.setGlobalTranslateFilter)
        self.danmuOption.fontSizeCombox.currentIndexChanged.connect(
            self.setGlobalFontSize)
        self.danmuButton = ToolButton(self.style().standardIcon(
            QStyle.SP_FileDialogDetailedView))
        self.danmuButton.clicked.connect(self.danmuOption.show)
        # self.danmuButton = PushButton(text='弹')
        # self.globalDanmuToken = True
        # self.danmuButton.clicked.connect(self.globalDanmuShow)
        self.controlBarLayout.addWidget(self.danmuButton, 0, 3, 1, 1)

        # 全局静音
        self.globalMuteToken = False
        self.volumeButton = PushButton(self.style().standardIcon(
            QStyle.SP_MediaVolume))
        self.volumeButton.clicked.connect(self.globalMediaMute)
        self.controlBarLayout.addWidget(self.volumeButton, 1, 0, 1, 1)
        # 全局音量滑条
        self.slider = Slider()
        self.slider.setValue(self.config['globalVolume'])
        self.slider.value.connect(self.globalSetVolume)
        self.controlBarLayout.addWidget(self.slider, 1, 1, 1, 3)
        progressText.setText('设置播放器控制...')

        # 添加主播按钮
        self.addButton = QPushButton('+')
        self.addButton.setFixedSize(160, 90)
        self.addButton.setStyleSheet('border:3px dotted #EEEEEE')
        self.addButton.setFont(QFont('Arial', 24, QFont.Bold))
        progressText.setText('设置添加控制...')
        self.controlBarLayout.addWidget(self.addButton, 2, 0, 1, 4)
        progressText.setText('设置全局控制...')

        self.scrollArea = ScrollArea()
        self.scrollArea.setStyleSheet('border-width:0px')
        # self.scrollArea.setMinimumHeight(111)
        self.cardDock = QDockWidget('卡片槽')
        self.cardDock.setWidget(self.scrollArea)
        self.cardDock.setFloating(False)
        self.cardDock.setAllowedAreas(Qt.LeftDockWidgetArea
                                      | Qt.RightDockWidgetArea
                                      | Qt.TopDockWidgetArea)
        self.addDockWidget(Qt.TopDockWidgetArea, self.cardDock)

        # self.controlBarLayout.addWidget(self.scrollArea, 3, 0, 1, 5)

        # 主播添加窗口
        self.liverPanel = LiverPanel(self.config['roomid'], application_path)
        self.liverPanel.addLiverRoomWidget.getHotLiver.start()
        self.liverPanel.addToWindow.connect(self.addCoverToPlayer)
        self.liverPanel.dumpConfig.connect(self.dumpConfig.start)  # 保存config
        self.liverPanel.refreshIDList.connect(
            self.refreshPlayerStatus)  # 刷新播放器
        self.scrollArea.setWidget(self.liverPanel)
        self.scrollArea.multipleTimes.connect(self.changeLiverPanelLayout)
        self.addButton.clicked.connect(self.liverPanel.openLiverRoomPanel)
        self.liverPanel.updatePlayingStatus(self.config['player'])
        progressText.setText('设置主播选择控制...')

        # ---- 菜单设置 ----
        self.optionMenu = self.menuBar().addMenu('设置')
        self.controlBarLayoutToken = self.config['control']
        layoutConfigAction = QAction('布局方式',
                                     self,
                                     triggered=self.openLayoutSetting)
        self.optionMenu.addAction(layoutConfigAction)
        globalQualityMenu = self.optionMenu.addMenu('全局画质 ►')
        originQualityAction = QAction(
            '原画', self, triggered=lambda: self.globalQuality(10000))
        globalQualityMenu.addAction(originQualityAction)
        bluerayQualityAction = QAction(
            '蓝光', self, triggered=lambda: self.globalQuality(400))
        globalQualityMenu.addAction(bluerayQualityAction)
        highQualityAction = QAction('超清',
                                    self,
                                    triggered=lambda: self.globalQuality(250))
        globalQualityMenu.addAction(highQualityAction)
        lowQualityAction = QAction('流畅',
                                   self,
                                   triggered=lambda: self.globalQuality(80))
        globalQualityMenu.addAction(lowQualityAction)
        globalAudioMenu = self.optionMenu.addMenu('全局音效 ►')
        audioOriginAction = QAction(
            '原始音效', self, triggered=lambda: self.globalAudioChannel(0))
        globalAudioMenu.addAction(audioOriginAction)
        audioDolbysAction = QAction(
            '杜比音效', self, triggered=lambda: self.globalAudioChannel(5))
        globalAudioMenu.addAction(audioDolbysAction)
        hardDecodeMenu = self.optionMenu.addMenu('解码方案 ►')
        hardDecodeAction = QAction('硬解',
                                   self,
                                   triggered=lambda: self.setDecode(True))
        hardDecodeMenu.addAction(hardDecodeAction)
        softDecodeAction = QAction('软解',
                                   self,
                                   triggered=lambda: self.setDecode(False))
        hardDecodeMenu.addAction(softDecodeAction)
        cacheSizeSetting = QAction('最大缓存设置',
                                   self,
                                   triggered=self.openCacheSizeSetting)
        self.optionMenu.addAction(cacheSizeSetting)
        startWithDanmuSetting = QAction(
            '自动加载弹幕设置', self, triggered=self.openStartWithDanmuSetting)
        self.optionMenu.addAction(startWithDanmuSetting)
        controlPanelAction = QAction('显示 / 隐藏控制条(H)',
                                     self,
                                     triggered=self.openControlPanel)
        self.optionMenu.addAction(controlPanelAction)
        self.fullScreenAction = QAction('全屏(F) / 退出(Esc)',
                                        self,
                                        triggered=self.fullScreen)
        self.optionMenu.addAction(self.fullScreenAction)
        exportConfig = QAction('导出预设', self, triggered=self.exportConfig)
        self.optionMenu.addAction(exportConfig)
        importConfig = QAction('导入预设', self, triggered=self.importConfig)
        self.optionMenu.addAction(importConfig)
        progressText.setText('设置选项菜单...')

        self.versionMenu = self.menuBar().addMenu('帮助')
        bilibiliAction = QAction('B站视频', self, triggered=self.openBilibili)
        self.versionMenu.addAction(bilibiliAction)
        hotKeyAction = QAction('快捷键', self, triggered=self.openHotKey)
        self.versionMenu.addAction(hotKeyAction)
        versionAction = QAction('检查版本', self, triggered=self.openVersion)
        self.versionMenu.addAction(versionAction)
        otherDDMenu = self.versionMenu.addMenu('其他DD系列工具 ►')
        DDSubtitleAction = QAction('DD烤肉机',
                                   self,
                                   triggered=self.openDDSubtitle)
        otherDDMenu.addAction(DDSubtitleAction)
        DDThanksAction = QAction('DD答谢机', self, triggered=self.openDDThanks)
        otherDDMenu.addAction(DDThanksAction)
        progressText.setText('设置帮助菜单...')

        self.payMenu = self.menuBar().addMenu('开源和投喂')
        githubAction = QAction('GitHub', self, triggered=self.openGithub)
        self.payMenu.addAction(githubAction)
        feedAction = QAction('投喂作者', self, triggered=self.openFeed)
        self.payMenu.addAction(feedAction)
        # killAction = QAction('自尽(测试)', self, triggered=lambda a: 0 / 0)
        # self.payMenu.addAction(killAction)
        progressText.setText('设置关于菜单...')

        # 鼠标和计时器
        self.oldMousePos = QPoint(0, 0)  # 初始化鼠标坐标
        self.hideMouseCnt = 90
        self.mouseTrackTimer = QTimer()
        self.mouseTrackTimer.timeout.connect(self.checkMousePos)
        self.mouseTrackTimer.start(100)  # 0.1s检测一次
        progressText.setText('设置UI...')
        self.checkDanmmuProvider = CheckDanmmuProvider()
        self.checkDanmmuProvider.start()
        logging.info('UI构造完毕')