Beispiel #1
0
class MainWindow(QMainWindow):
    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构造完毕')

    def setPlayer(self):
        for index, layoutConfig in enumerate(self.config['layout']):
            roomID = self.config['player'][index]
            videoWidget = self.videoWidgetList[index]
            videoWidget.roomID = str(roomID)  # 转一下防止格式出错
            y, x, h, w = layoutConfig
            self.mainLayout.addWidget(videoWidget, y, x, h, w)
            self.videoWidgetList[index].show()
        self.videoIndex = 0
        self.setMediaTimer = QTimer()
        self.setMediaTimer.timeout.connect(self.setMedia)
        # self.setMediaTimer.start(500)  # QMediaPlayer加载慢一点 否则容易崩
        self.setMediaTimer.start(10)  # vlc

    def setMedia(self):
        if self.videoIndex == 9:
            self.setMediaTimer.stop()
        elif self.videoIndex < len(self.config['layout']):
            # pass
            self.videoWidgetList[self.videoIndex].mediaReload()
        else:
            self.videoWidgetList[self.videoIndex].player.stop()
        self.videoIndex += 1

    def addMedia(self, info):  # 窗口 房号
        id, roomID = info
        self.config['player'][id] = roomID
        self.dumpConfig.start()

    def deleteMedia(self, id):
        self.config['player'][id] = 0
        self.dumpConfig.start()

    def exchangeMedia(self, info):  # 交换播放窗口的函数
        fromID, fromRoomID, toID, toRoomID = info  # 交换数据
        fromVideo, toVideo = self.videoWidgetList[
            fromID], self.videoWidgetList[toID]  # 待交换的两个控件
        fromVideo.id, toVideo.id = toID, fromID  # 交换id
        fromVideo.topLabel.setText(fromVideo.topLabel.text().replace(
            '窗口%s' % (fromID + 1), '窗口%s' % (toID + 1)))
        toVideo.topLabel.setText(toVideo.topLabel.text().replace(
            '窗口%s' % (toID + 1), '窗口%s' % (fromID + 1)))
        # fromMuted, toMuted = self.config['muted'][fromID], self.config['muted'][toID]  # 获取原来的静音设置
        # fromVolume, toVolume = self.config['volume'][fromID], self.config['volume'][toID]  # 获取原来的音量
        fromVideoPos = fromVideo.mapToGlobal(
            fromVideo.videoFrame.pos())  # 保持弹幕框相对位置
        toVideoPos = toVideo.mapToGlobal(toVideo.videoFrame.pos())
        fromTextDelta, toTextDelta = fromVideo.textPosDelta, toVideo.textPosDelta
        fromVideo.textBrowser.move(toVideoPos + fromTextDelta)
        toVideo.textBrowser.move(fromVideoPos + toTextDelta)
        # fromVideo.mediaMute(toMuted)  # 交换静音设置
        # fromVideo.setVolume(toVolume)  # 交换音量
        # toVideo.mediaMute(fromMuted)
        # toVideo.setVolume(fromVolume)
        self.videoWidgetList[fromID], self.videoWidgetList[
            toID] = toVideo, fromVideo  # 交换控件列表
        self.config['player'][toID] = fromRoomID  # 记录config
        self.config['player'][fromID] = toRoomID
        self.dumpConfig.start()
        self.changeLayout(self.config['layout'])  # 刷新layout

    def setDanmu(self):
        self.dumpConfig.start()

    def setTranslator(self, info):
        id, token = info  # 窗口 同传显示布尔值
        self.config['translator'][id] = token
        self.dumpConfig.start()

    def setQuality(self, info):
        id, quality = info  # 窗口 画质
        self.config['quality'][id] = quality
        self.dumpConfig.start()

    def setAudioChannel(self, info):
        id, audioChannel = info  # 窗口 音效
        self.config['audioChannel'][id] = audioChannel
        self.dumpConfig.start()

    def popWindow(self, info):  # 悬浮窗播放
        id, roomID, quality, showMax, startWithDanmu = info
        logging.info("%s 进入悬浮窗模式, 弹幕?: %s" % (roomID, startWithDanmu))
        self.popVideoWidgetList[id].roomID = roomID
        self.popVideoWidgetList[id].quality = quality
        self.popVideoWidgetList[id].resize(1280, 720)
        self.popVideoWidgetList[id].show()
        if startWithDanmu:
            self.popVideoWidgetList[id].showDanmu()
            self.popVideoWidgetList[id].textBrowser.show()
        if showMax:
            self.popVideoWidgetList[id].showMaximized()
        self.popVideoWidgetList[id].mediaReload()

    def mutedChanged(self, mutedInfo):
        id, muted = mutedInfo
        token = 2 if muted else 1
        self.config['muted'][id] = token
        self.dumpConfig.start()

    def volumeChanged(self, volumeInfo):
        id, value = volumeInfo
        self.config['volume'][id] = value
        self.dumpConfig.start()

    def globalMediaPlay(self):
        if self.globalPlayToken:
            force = 1
            self.play.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
        else:
            force = 2
            self.play.setIcon(self.style().standardIcon(QStyle.SP_MediaPause))
        self.globalPlayToken = not self.globalPlayToken
        for videoWidget in self.videoWidgetList:
            videoWidget.mediaPlay(force)

    def globalMediaReload(self):
        for videoWidget in self.videoWidgetList:
            if not videoWidget.isHidden():
                videoWidget.mediaReload()

    def globalMediaMute(self):
        if self.globalMuteToken:
            force = 1
            self.volumeButton.setIcon(self.style().standardIcon(
                QStyle.SP_MediaVolume))
        else:
            force = 2
            self.volumeButton.setIcon(self.style().standardIcon(
                QStyle.SP_MediaVolumeMuted))
        self.globalMuteToken = not self.globalMuteToken
        for videoWidget in self.videoWidgetList:
            videoWidget.mediaMute(force)
        self.config['muted'] = [force] * 9
        # self.dumpConfig.start()

    def globalSetVolume(self, value):
        for videoWidget in self.videoWidgetList:
            videoWidget.player.audio_set_volume(
                int(value * videoWidget.volumeAmplify))
            videoWidget.volume = value
            videoWidget.slider.setValue(value)
        self.config['volume'] = [value] * 9
        self.config['globalVolume'] = value
        # self.dumpConfig.start()

    def globalMediaStop(self):
        for videoWidget in self.videoWidgetList:
            videoWidget.mediaStop()

    def globalQuality(self, quality):
        for videoWidget in self.videoWidgetList:
            if not videoWidget.isHidden():  # 窗口没有被隐藏
                videoWidget.quality = quality
                videoWidget.mediaReload()
        self.config['quality'] = [quality] * 9
        self.dumpConfig.start()

    def globalAudioChannel(self, audioChannel):
        for videoWidget in self.videoWidgetList:
            videoWidget.audioChannel = audioChannel
            videoWidget.player.audio_set_channel(audioChannel)
        self.config['audioChannel'] = [audioChannel] * 9
        # self.dumpConfig.start()

    def setDecode(self, hardwareDecodeToken):
        for videoWidget in self.videoWidgetList:
            videoWidget.hardwareDecode = hardwareDecodeToken
        self.globalMediaReload()
        self.config['hardwareDecode'] = hardwareDecodeToken

    def openControlPanel(self):
        self.controlBar.hide(
        ) if self.controlBarToken else self.controlBar.show()
        self.controlBarToken = not self.controlBarToken
        self.config['control'] = self.controlBarToken
        # self.dumpConfig.start()

    def openVersion(self):
        self.version.hide()
        self.version.show()

    def openGithub(self):
        QDesktopServices.openUrl(
            QUrl(r'https://github.com/zhimingshenjun/DD_Monitor'))

    def openBilibili(self):
        QDesktopServices.openUrl(
            QUrl(r'https://www.bilibili.com/video/BV1yo4y1d7iP'))

    def openDDSubtitle(self):
        QDesktopServices.openUrl(
            QUrl(r'https://www.bilibili.com/video/BV1p5411b7o7'))

    def openDDThanks(self):
        QDesktopServices.openUrl(
            QUrl(r'https://www.bilibili.com/video/BV1Di4y1L7T2'))

    def openCacheSizeSetting(self):
        userInputCache, okPressed = QInputDialog.getInt(
            self, "设置最大缓存大小", "最大缓存(GB)",
            int(float(self.config['maxCacheSize']) / (10**6)), 1, 4, 1)
        if okPressed:
            self.config['maxCacheSize'] = int(userInputCache * 10**6)
            self.dumpConfig.start()
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Information)
            msg.setText("缓存大小设置成功,重启监控室后生效。")
            msg.exec()

    def openStartWithDanmuSetting(self):
        items = ('加载(推荐,默认。但可能增加网络压力,可能会被限流。)', '不加载')
        defulatSelection = 0
        if not self.config['startWithDanmu']:
            defulatSelection = 1
        selection, okPressed = QInputDialog.getItem(self, "设置启动时是否加载弹幕",
                                                    "加载选项", items,
                                                    defulatSelection, False)
        if okPressed:
            trueDanmu = (selection == items[0])
            self.config['startWithDanmu'] = trueDanmu
            self.dumpConfig.start()

    def openHotKey(self):
        self.hotKey.hide()
        self.hotKey.show()

    def openFeed(self):
        self.pay.hide()
        self.pay.show()

    def checkMousePos(self):
        for videoWidget in self.videoWidgetList:  # vlc的播放会直接音量最大化 实在没地方放了 写在这里实时强制修改它的音量
            videoWidget.player.audio_set_volume(
                int(videoWidget.volume * videoWidget.volumeAmplify))
        newMousePos = QCursor.pos()
        if newMousePos != self.oldMousePos:
            self.setCursor(Qt.ArrowCursor)  # 鼠标动起来就显示
            self.oldMousePos = newMousePos
            self.hideMouseCnt = 20  # 刷新隐藏鼠标的间隔
        if self.hideMouseCnt > 0:
            self.hideMouseCnt -= 1
        else:
            self.setCursor(Qt.BlankCursor)  # 计数归零隐藏鼠标
            for videoWidget in self.videoWidgetList:
                videoWidget.topLabel.hide()  # 隐藏播放窗口的控制条
                videoWidget.frame.hide()
            for videoWidget in self.popVideoWidgetList:
                videoWidget.topLabel.hide()  # 隐藏悬浮窗口的控制条
                videoWidget.frame.hide()

    def moveEvent(self, QMoveEvent):  # 捕获主窗口moveEvent来实时同步弹幕机位置
        for videoWidget in self.videoWidgetList:
            videoPos = videoWidget.mapToGlobal(
                videoWidget.videoFrame.pos())  # videoFrame的坐标要转成globalPos
            videoWidget.textBrowser.move(videoPos + videoWidget.textPosDelta)
            videoWidget.textPosDelta = videoWidget.textBrowser.pos() - videoPos

    def changeEvent(self, QEvent):  # 当用户最小化界面的时候把弹幕机也隐藏了
        try:
            if self.isMinimized():
                for videoWidget in self.videoWidgetList:
                    videoWidget.textBrowser.hide()
            else:
                for index, videoWidget in enumerate(self.videoWidgetList):
                    if self.config['danmu'][index][
                            0] and not videoWidget.isHidden():  # 显示弹幕机
                        videoWidget.textBrowser.show()
        except:
            pass

    def closeEvent(self, QCloseEvent):
        self.hide()
        self.layoutSettingPanel.close()
        self.liverPanel.addLiverRoomWidget.close()
        for videoWidget in self.videoWidgetList:
            videoWidget.player.stop()
            videoWidget.getMediaURL.recordToken = False  # 关闭缓存并清除
            videoWidget.getMediaURL.checkTimer.stop()
            videoWidget.checkPlaying.stop()
        for videoWidget in self.popVideoWidgetList:  # 关闭悬浮窗
            videoWidget.player.stop()
            videoWidget.getMediaURL.recordToken = False
            videoWidget.getMediaURL.checkTimer.stop()
            videoWidget.checkPlaying.stop()
            videoWidget.close()
        self.dumpConfig.start()

    def openLayoutSetting(self):
        self.layoutSettingPanel.hide()
        self.layoutSettingPanel.show()

    def changeLayout(self, layoutConfig):
        for videoWidget in self.videoWidgetList:
            videoWidget.mediaPlay(1)  # 全部暂停
        for index, _ in enumerate(self.config['layout']):
            self.videoWidgetList[index].textBrowser.hide()
            self.mainLayout.itemAt(0).widget().hide()
            self.mainLayout.removeWidget(self.mainLayout.itemAt(0).widget())
        for index, layout in enumerate(layoutConfig):
            y, x, h, w = layout
            videoWidget = self.videoWidgetList[index]
            videoWidget.show()
            if videoWidget.textSetting[0]:  # 显示弹幕
                videoWidget.textBrowser.show()
            self.mainLayout.addWidget(videoWidget, y, x, h, w)
            if videoWidget.roomID != '0':
                videoWidget.mediaPlay(2)  # 显示的窗口播放
        for videoWidget in self.videoWidgetList[index + 1:]:  # 被隐藏起来的窗口
            videoWidget.getMediaURL.recordToken = False  # 关闭缓存并清除
            videoWidget.getMediaURL.checkTimer.stop()
            videoWidget.checkPlaying.stop()
        self.config['layout'] = layoutConfig
        self.dumpConfig.start()

    def fullScreen(self):
        if self.isFullScreen():  # 退出全屏
            if self.maximumToken:
                self.showMaximized()
            else:
                self.showNormal()
            self.optionMenu.menuAction().setVisible(True)
            self.versionMenu.menuAction().setVisible(True)
            self.payMenu.menuAction().setVisible(True)
            if self.controlBarToken:
                self.controlBar.show()
        else:  # 全屏
            for videoWidget in self.videoWidgetList:
                videoWidget.fullScreen = True
            self.maximumToken = self.isMaximized()
            self.optionMenu.menuAction().setVisible(False)
            self.versionMenu.menuAction().setVisible(False)
            self.payMenu.menuAction().setVisible(False)
            if self.controlBarToken:
                self.controlBar.hide()
            for videoWidget in self.videoWidgetList:
                videoWidget.fullScreen = True
            self.showFullScreen()

    def exportConfig(self):
        self.savePath = QFileDialog.getSaveFileName(self, "选择保存路径", 'DD监控室预设',
                                                    "*.json")[0]
        if self.savePath:  # 保存路径有效
            try:
                with open(self.savePath, 'w') as f:
                    f.write(json.dumps(self.config, ensure_ascii=False))
                QMessageBox.information(self, '导出预设', '导出完成', QMessageBox.Ok)
            except Exception as e:
                logging.error(str(e))

    def importConfig(self):
        jsonPath = QFileDialog.getOpenFileName(self, "选择预设", None, "*.json")[0]
        if jsonPath:
            if os.path.getsize(jsonPath):
                config = {}
                try:
                    config = json.loads(open(jsonPath).read())
                except:
                    config = {}
                if config:  # 如果能成功读取到config文件
                    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
                    self.liverPanel.addLiverRoomList(
                        self.config['roomid'].keys())
                    QMessageBox.information(self, '导入预设', '导入完成',
                                            QMessageBox.Ok)

    def muteExcept(self):
        for videoWidget in self.videoWidgetList:
            if videoWidget.hoverToken:
                videoWidget.mediaMute(1)  # 取消静音
            else:
                videoWidget.mediaMute(2)  # 静音

    def keyPressEvent(self, QEvent):
        if QEvent.key() == Qt.Key_Escape:
            if self.maximumToken:
                self.showMaximized()
            else:
                self.showNormal()
            self.optionMenu.menuAction().setVisible(True)
            self.versionMenu.menuAction().setVisible(True)
            if self.controlBarToken:
                self.controlBar.show()
        elif QEvent.key() == Qt.Key_F:
            self.fullScreen()
        elif QEvent.key() == Qt.Key_H:
            self.openControlPanel()
        elif QEvent.key() == Qt.Key_M:
            self.muteExcept()

    def addCoverToPlayer(self, info):  # 窗口 房号
        self.addMedia(info)
        self.videoWidgetList[info[0]].roomID = info[1]  # 修改房号
        self.videoWidgetList[info[0]].mediaReload()  # 重载视频

    def refreshPlayerStatus(self, refreshIDList):  # 刷新直播状态发生变化的播放器
        for videoWidget in self.videoWidgetList:
            for roomID in refreshIDList:
                if roomID == videoWidget.roomID:
                    videoWidget.mediaReload()
                    break
Beispiel #2
0
class MainWindow(QMainWindow):
    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构造完毕')

    def setPlayer(self):
        for index, layoutConfig in enumerate(self.config['layout']):
            roomID = self.config['player'][index]
            videoWidget = self.videoWidgetList[index]
            videoWidget.roomID = str(roomID)  # 转一下防止格式出错
            y, x, h, w = layoutConfig
            self.mainLayout.addWidget(videoWidget, y, x, h, w)
            self.videoWidgetList[index].show()
        self.videoIndex = 0
        self.setMediaTimer = QTimer()
        self.setMediaTimer.timeout.connect(self.setMedia)
        # self.setMediaTimer.start(500)  # QMediaPlayer加载慢一点 否则容易崩
        self.setMediaTimer.start(10)  # vlc

    def setMedia(self):
        if self.videoIndex == 9:
            self.setMediaTimer.stop()
        elif self.videoIndex < len(self.config['layout']):
            # pass
            self.videoWidgetList[self.videoIndex].mediaReload()
        else:
            self.videoWidgetList[self.videoIndex].playerRestart()
        self.videoIndex += 1

    def addMedia(self, info):  # 窗口 房号
        id, roomID = info
        self.config['player'][id] = roomID
        self.liverPanel.updatePlayingStatus(self.config['player'])
        self.dumpConfig.start()

    def deleteMedia(self, id):
        self.config['player'][id] = 0
        self.liverPanel.updatePlayingStatus(self.config['player'])
        self.dumpConfig.start()

    def exchangeMedia(self, info):  # 交换播放窗口的函数
        fromID, fromRoomID, toID, toRoomID = info  # 交换数据
        fromVideo, toVideo = self.videoWidgetList[
            fromID], self.videoWidgetList[toID]  # 待交换的两个控件
        fromVideo.id, toVideo.id = toID, fromID  # 交换id
        fromVideo.topLabel.setText(fromVideo.topLabel.text().replace(
            '窗口%s' % (fromID + 1), '窗口%s' % (toID + 1)))
        toVideo.topLabel.setText(toVideo.topLabel.text().replace(
            '窗口%s' % (toID + 1), '窗口%s' % (fromID + 1)))

        fromWidth, fromHeight = fromVideo.width(), fromVideo.height()
        toWidth, toHeight = toVideo.width(), toVideo.height()
        if 3 < abs(fromWidth -
                   toWidth) or 3 < abs(fromHeight -
                                       toHeight):  # 有主次关系的播放窗交换同时交换音量
            fromMuted = 2 if fromVideo.player.audio_get_mute() else 1
            toMuted = 2 if toVideo.player.audio_get_mute() else 1
            fromVolume, toVolume = fromVideo.player.audio_get_volume(
            ), toVideo.player.audio_get_volume()  # 音量值
            fromVideo.mediaMute(toMuted)  # 交换静音设置
            fromVideo.setVolume(toVolume)  # 交换音量
            toVideo.mediaMute(fromMuted)
            toVideo.setVolume(fromVolume)
        self.videoWidgetList[fromID], self.videoWidgetList[
            toID] = toVideo, fromVideo  # 交换控件列表
        self.config['player'][toID] = fromRoomID  # 记录config
        self.config['player'][fromID] = toRoomID
        self.dumpConfig.start()
        # self.changeLayout(self.config['layout'])  # 刷新layout
        fromLayout, toLayout = self.config['layout'][fromID], self.config[
            'layout'][toID]  # 用新的方法直接交换两个窗口
        y, x, h, w = fromLayout
        self.mainLayout.addWidget(toVideo, y, x, h, w)
        y, x, h, w = toLayout
        self.mainLayout.addWidget(fromVideo, y, x, h, w)

        # TODO: 改崩溃了 不想改了 怎么改都没法按比例调整弹幕窗坐标
        # fromVideoPos = fromVideo.mapToGlobal(fromVideo.pos())  # 保持弹幕框相对位置
        # toVideoPos = toVideo.mapToGlobal(toVideo.pos())
        # fromVideo.textBrowser.move(toVideoPos + QPoint(toWidth * fromVideo.deltaX, toHeight * fromVideo.deltaY))
        # toVideo.textBrowser.move(fromVideoPos + QPoint(fromWidth * toVideo.deltaX, fromHeight * toVideo.deltaY))

    def setDanmu(self):
        self.dumpConfig.start()

    def setTranslator(self, info):
        id, token = info  # 窗口 同传显示布尔值
        self.config['translator'][id] = token
        self.dumpConfig.start()

    def setQuality(self, info):
        id, quality = info  # 窗口 画质
        self.config['quality'][id] = quality
        self.dumpConfig.start()

    def setAudioChannel(self, info):
        id, audioChannel = info  # 窗口 音效
        self.config['audioChannel'][id] = audioChannel
        self.dumpConfig.start()

    def popWindow(self, info):  # 悬浮窗播放
        id, roomID, quality, showMax, startWithDanmu = info
        logging.info("%s 进入悬浮窗模式, 弹幕?: %s" % (roomID, startWithDanmu))
        self.popVideoWidgetList[id].roomID = roomID
        self.popVideoWidgetList[id].quality = quality
        self.popVideoWidgetList[id].resize(1280, 720)
        self.popVideoWidgetList[id].show()
        if startWithDanmu:
            self.popVideoWidgetList[id].showDanmu()
            self.popVideoWidgetList[id].textBrowser.show()
        if showMax:
            self.popVideoWidgetList[id].showMaximized()
        self.popVideoWidgetList[id].mediaReload()

    def mutedChanged(self, mutedInfo):
        id, muted = mutedInfo
        token = 2 if muted else 1
        self.config['muted'][id] = token
        # self.dumpConfig.start()

    def volumeChanged(self, volumeInfo):
        id, value = volumeInfo
        self.config['volume'][id] = value
        # self.dumpConfig.start()

    def globalMediaPlay(self):
        if self.globalPlayToken:
            force = 1
            self.play.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
        else:
            force = 2
            self.play.setIcon(self.style().standardIcon(QStyle.SP_MediaPause))
        self.globalPlayToken = not self.globalPlayToken
        for videoWidget in self.videoWidgetList:
            videoWidget.mediaPlay(force)

    def globalMediaReload(self):
        for videoWidget in self.videoWidgetList:
            if not videoWidget.isHidden():
                videoWidget.mediaReload()

    def globalMediaMute(self):
        if self.globalMuteToken:
            force = 1
            self.volumeButton.setIcon(self.style().standardIcon(
                QStyle.SP_MediaVolume))
        else:
            force = 2
            self.volumeButton.setIcon(self.style().standardIcon(
                QStyle.SP_MediaVolumeMuted))
        self.globalMuteToken = not self.globalMuteToken
        for videoWidget in self.videoWidgetList:
            videoWidget.mediaMute(force)
        self.config['muted'] = [force] * 9
        # self.dumpConfig.start()

    def globalSetVolume(self, value):
        for videoWidget in self.videoWidgetList:
            videoWidget.player.audio_set_volume(
                int(value * videoWidget.volumeAmplify))
            videoWidget.volume = value
            videoWidget.slider.setValue(value)
        self.config['volume'] = [value] * 9
        self.config['globalVolume'] = value
        # self.dumpConfig.start()

    def globalMediaStop(self):
        for videoWidget in self.videoWidgetList:
            videoWidget.mediaStop()

    # def globalDanmuShow(self):  # 已弃用
    #     self.globalDanmuToken = not self.globalDanmuToken
    #     for videoWidget in self.videoWidgetList:
    #         if not videoWidget.isHidden():
    #             videoWidget.textBrowser.show() if self.globalDanmuToken else videoWidget.textBrowser.hide()
    #     for danmuConfig in self.config['danmu']:
    #         danmuConfig[0] = self.globalDanmuToken

    def setGlobalDanmuOpacity(self, value):
        if value < 7: value = 7  # 最小透明度
        opacity = int(value / 101 * 256)
        color = str(hex(opacity))[2:] + '000000'
        for videoWidget in self.videoWidgetList + self.popVideoWidgetList:
            videoWidget.textSetting[1] = value  # 记录设置
            videoWidget.textBrowser.textBrowser.setStyleSheet(
                'background-color:#%s' % color)
            videoWidget.textBrowser.transBrowser.setStyleSheet(
                'background-color:#%s' % color)
            videoWidget.setDanmu.emit()

    def setGlobalHorizontalPercent(self, index):  # 设置弹幕框水平宽度
        for videoWidget in self.videoWidgetList + self.popVideoWidgetList:
            videoWidget.textSetting[2] = index
            videoWidget.horiPercent = [
                0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0
            ][index]  # 记录横向占比
            width = videoWidget.width() * videoWidget.horiPercent
            videoWidget.textBrowser.resize(width,
                                           videoWidget.textBrowser.height())
            videoWidget.textBrowser.textBrowser.verticalScrollBar().setValue(
                100000000)
            videoWidget.textBrowser.transBrowser.verticalScrollBar().setValue(
                100000000)
            videoWidget.setDanmu.emit()

    def setGlobalVerticalPercent(self, index):  # 设置弹幕框垂直高度
        for videoWidget in self.videoWidgetList + self.popVideoWidgetList:
            videoWidget.textSetting[3] = index
            videoWidget.vertPercent = [
                0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0
            ][index]  # 记录纵向占比
            height = videoWidget.height() * videoWidget.vertPercent
            videoWidget.textBrowser.resize(videoWidget.textBrowser.width(),
                                           height)
            videoWidget.textBrowser.textBrowser.verticalScrollBar().setValue(
                100000000)
            videoWidget.textBrowser.transBrowser.verticalScrollBar().setValue(
                100000000)
            videoWidget.setDanmu.emit()

    def setGlobalTranslateBrowser(self, index):
        for videoWidget in self.videoWidgetList + self.popVideoWidgetList:
            videoWidget.textSetting[4] = index
            if index == 0:  # 显示弹幕和同传
                videoWidget.textBrowser.textBrowser.show()
                videoWidget.textBrowser.transBrowser.show()
            elif index == 1:  # 只显示弹幕
                videoWidget.textBrowser.transBrowser.hide()
                videoWidget.textBrowser.textBrowser.show()
            elif index == 2:  # 只显示同传
                videoWidget.textBrowser.textBrowser.hide()
                videoWidget.textBrowser.transBrowser.show()
            width = videoWidget.width() * videoWidget.horiPercent
            height = videoWidget.height() * videoWidget.vertPercent
            videoWidget.textBrowser.resize(width, height)
            videoWidget.setDanmu.emit()

    def setGlobalTranslateFilter(self, filterWords):
        for videoWidget in self.videoWidgetList + self.popVideoWidgetList:
            videoWidget.textSetting[5] = filterWords
            videoWidget.filters = filterWords.split(' ')
            videoWidget.setDanmu.emit()

    def setGlobalFontSize(self, index):
        for videoWidget in self.videoWidgetList + self.popVideoWidgetList:
            videoWidget.textSetting[6] = index
            videoWidget.textBrowser.textBrowser.setFont(
                QFont('Microsoft JhengHei', index + 5, QFont.Bold))
            videoWidget.textBrowser.transBrowser.setFont(
                QFont('Microsoft JhengHei', index + 5, QFont.Bold))
            videoWidget.setDanmu.emit()

    def globalQuality(self, quality):
        for videoWidget in self.videoWidgetList:
            if not videoWidget.isHidden():  # 窗口没有被隐藏
                videoWidget.quality = quality
                videoWidget.mediaReload()
        self.config['quality'] = [quality] * 9
        self.dumpConfig.start()

    def globalAudioChannel(self, audioChannel):
        for videoWidget in self.videoWidgetList:
            videoWidget.audioChannel = audioChannel
            videoWidget.player.audio_set_channel(audioChannel)
        self.config['audioChannel'] = [audioChannel] * 9
        # self.dumpConfig.start()

    def setDecode(self, hardwareDecodeToken):
        for videoWidget in self.videoWidgetList:
            videoWidget.hardwareDecode = hardwareDecodeToken
        self.globalMediaReload()
        self.config['hardwareDecode'] = hardwareDecodeToken

    def openControlPanel(self):
        if self.controlDock.isHidden() and self.cardDock.isHidden():
            self.controlDock.show()
            self.cardDock.show()
            self.optionMenu.menuAction().setVisible(True)
            self.versionMenu.menuAction().setVisible(True)
            self.payMenu.menuAction().setVisible(True)
        else:
            self.controlDock.hide()
            self.cardDock.hide()
            self.optionMenu.menuAction().setVisible(False)
            self.versionMenu.menuAction().setVisible(False)
            self.payMenu.menuAction().setVisible(False)
        self.controlBarLayoutToken = self.controlDock.isHidden()

    def openVersion(self):
        self.version.hide()
        self.version.show()

    def openGithub(self):
        QDesktopServices.openUrl(
            QUrl(r'https://github.com/zhimingshenjun/DD_Monitor'))

    def openBilibili(self):
        QDesktopServices.openUrl(
            QUrl(r'https://www.bilibili.com/video/BV1yo4y1d7iP'))

    def openDDSubtitle(self):
        QDesktopServices.openUrl(
            QUrl(r'https://www.bilibili.com/video/BV1p5411b7o7'))

    def openDDThanks(self):
        QDesktopServices.openUrl(
            QUrl(r'https://www.bilibili.com/video/BV1Di4y1L7T2'))

    def openCacheSizeSetting(self):
        userInputCache, okPressed = QInputDialog.getInt(
            self, "设置最大缓存大小", "最大缓存(GB)",
            int(float(self.config['maxCacheSize']) / (10**6)), 1, 4, 1)
        if okPressed:
            self.config['maxCacheSize'] = int(userInputCache * 10**6)
            self.dumpConfig.start()
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Information)
            msg.setText("缓存大小设置成功,重启监控室后生效。")
            msg.exec()

    def openStartWithDanmuSetting(self):
        items = ('加载(推荐,默认。但可能增加网络压力,可能会被限流。)', '不加载')
        defulatSelection = 0
        if not self.config['startWithDanmu']:
            defulatSelection = 1
        selection, okPressed = QInputDialog.getItem(self, "设置启动时是否加载弹幕",
                                                    "加载选项", items,
                                                    defulatSelection, False)
        if okPressed:
            trueDanmu = (selection == items[0])
            self.config['startWithDanmu'] = trueDanmu
            self.dumpConfig.start()

    def openHotKey(self):
        self.hotKey.hide()
        self.hotKey.show()

    def openFeed(self):
        self.pay.hide()
        self.pay.show()
        self.pay.thankToBoss.start()

    def checkMousePos(self):
        # for videoWidget in self.videoWidgetList:  # vlc的播放会直接音量最大化 实在没地方放了 写在这里实时强制修改它的音量
        #     videoWidget.player.audio_set_volume(int(videoWidget.volume * videoWidget.volumeAmplify))
        newMousePos = QCursor.pos()
        if newMousePos != self.oldMousePos:
            self.setCursor(Qt.ArrowCursor)  # 鼠标动起来就显示
            self.oldMousePos = newMousePos
            self.hideMouseCnt = 20  # 刷新隐藏鼠标的间隔
        if self.hideMouseCnt > 0:
            self.hideMouseCnt -= 1
        else:
            self.setCursor(Qt.BlankCursor)  # 计数归零隐藏鼠标
            for videoWidget in self.videoWidgetList:
                videoWidget.topLabel.hide()  # 隐藏播放窗口的控制条
                videoWidget.frame.hide()
            for videoWidget in self.popVideoWidgetList:
                videoWidget.topLabel.hide()  # 隐藏悬浮窗口的控制条
                videoWidget.frame.hide()

    def moveEvent(self, QMoveEvent):  # 捕获主窗口moveEvent来实时同步弹幕机位置
        for videoWidget in self.videoWidgetList:
            videoPos = videoWidget.mapToGlobal(
                videoWidget.videoFrame.pos())  # videoFrame的坐标要转成globalPos
            videoWidget.textBrowser.move(videoPos + videoWidget.textPosDelta)
            videoWidget.textPosDelta = videoWidget.textBrowser.pos() - videoPos

    def hideEvent(self, e: QHideEvent) -> None:
        """主窗口隐藏:关闭、最小化
        隐藏所有弹幕机
        """
        logging.debug(f"主窗口已隐藏")
        for videoWidget in self.videoWidgetList:
            videoWidget.textBrowser.hide()

    def showEvent(self, e: QShowEvent) -> None:
        """主窗口显示:打开、最大化
        显示开启的弹幕机
        """
        logging.debug(f"主窗口已显示")
        for index, videoWidget in enumerate(self.videoWidgetList):
            if self.config['danmu'][index][0] and not videoWidget.isHidden():
                videoWidget.textBrowser.show()

    def closeEvent(self, QCloseEvent):
        self.hide()
        self.layoutSettingPanel.close()
        self.liverPanel.addLiverRoomWidget.close()
        for videoWidget in self.videoWidgetList:
            videoWidget.getMediaURL.recordToken = False  # 关闭缓存并清除
            videoWidget.getMediaURL.checkTimer.stop()
            videoWidget.checkPlaying.stop()
            videoWidget.close()
        for videoWidget in self.popVideoWidgetList:  # 关闭悬浮窗
            videoWidget.getMediaURL.recordToken = False
            videoWidget.getMediaURL.checkTimer.stop()
            videoWidget.checkPlaying.stop()
            videoWidget.close()
        self.dumpConfig.start()

    def openLayoutSetting(self):
        self.layoutSettingPanel.hide()
        self.layoutSettingPanel.show()

    def changeLayout(self, layoutConfig):
        for videoWidget in self.videoWidgetList:
            videoWidget.mediaPlay(1)  # 全部暂停
        for index, _ in enumerate(self.config['layout']):
            self.videoWidgetList[index].textBrowser.hide()
            self.mainLayout.itemAt(0).widget().hide()
            self.mainLayout.removeWidget(self.mainLayout.itemAt(0).widget())
        for index, layout in enumerate(layoutConfig):
            y, x, h, w = layout
            videoWidget = self.videoWidgetList[index]
            videoWidget.show()
            if videoWidget.textSetting[0]:  # 显示弹幕
                videoWidget.textBrowser.show()
            self.mainLayout.addWidget(videoWidget, y, x, h, w)
            if videoWidget.roomID != '0':
                videoWidget.mediaPlay(2)  # 显示的窗口播放
        for videoWidget in self.videoWidgetList[index + 1:]:  # 被隐藏起来的窗口
            videoWidget.getMediaURL.recordToken = False  # 关闭缓存并清除
            videoWidget.getMediaURL.checkTimer.stop()
            videoWidget.checkPlaying.stop()
        self.config['layout'] = layoutConfig
        self.dumpConfig.start()

    def changeLiverPanelLayout(self, multiple):
        self.liverPanel.multiple = multiple
        self.liverPanel.refreshPanel()

    def fullScreen(self):
        if self.isFullScreen():  # 退出全屏
            if self.maximumToken:
                self.showMaximized()
            else:
                self.showNormal()
            self.optionMenu.menuAction().setVisible(True)
            self.versionMenu.menuAction().setVisible(True)
            self.payMenu.menuAction().setVisible(True)
            if self.controlBarLayoutToken:
                self.controlDock.show()
                self.cardDock.show()
        else:  # 全屏
            for videoWidget in self.videoWidgetList:
                videoWidget.fullScreen = True
            self.maximumToken = self.isMaximized()
            self.optionMenu.menuAction().setVisible(False)
            self.versionMenu.menuAction().setVisible(False)
            self.payMenu.menuAction().setVisible(False)
            if self.controlBarLayoutToken:
                self.controlDock.hide()
                self.cardDock.hide()
            for videoWidget in self.videoWidgetList:
                videoWidget.fullScreen = True
            self.showFullScreen()

    def exportConfig(self):
        self.savePath = QFileDialog.getSaveFileName(self, "选择保存路径", 'DD监控室预设',
                                                    "*.json")[0]
        if self.savePath:  # 保存路径有效
            try:
                with codecs.open(self.savePath, 'w', encoding='utf-8') as f:
                    f.write(json.dumps(self.config, ensure_ascii=False))
                QMessageBox.information(self, '导出预设', '导出完成', QMessageBox.Ok)
            except:
                logging.exception('json 配置导出失败')

    def importConfig(self):
        jsonPath = QFileDialog.getOpenFileName(self, "选择预设", None, "*.json")[0]
        if jsonPath:
            if os.path.getsize(jsonPath):
                config = {}
                try:
                    with codecs.open(jsonPath, 'r', encoding='utf-8') as f:
                        config = json.loads(f.read())
                except UnicodeDecodeError:
                    try:
                        with codecs.open(jsonPath, 'r', encoding='gbk') as f:
                            config = json.loads(f.read())
                    except:
                        logging.exception('json 配置导入失败')
                        config = {}
                except:
                    logging.exception('json 配置导入失败')
                    config = {}
                if config:  # 如果能成功读取到config文件
                    # config['roomid'].update(self.config['roomid'])  # 添加现有直播间
                    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
                    self.liverPanel.addLiverRoomList(
                        self.config['roomid'].keys())
                    QMessageBox.information(self, '导入预设', '导入完成',
                                            QMessageBox.Ok)

    def muteExcept(self):
        if not self.soloToken:
            for videoWidget in self.videoWidgetList:
                if not videoWidget.isHidden() and videoWidget.hoverToken:
                    videoWidget.mediaMute(1)  # 取消静音
                else:
                    videoWidget.mediaMute(2)  # 静音
        else:  # 恢复所有直播间声音
            for videoWidget in self.videoWidgetList:
                if not videoWidget.isHidden():
                    videoWidget.mediaMute(1)  # 取消静音
        self.soloToken = not self.soloToken

    def closePopWindow(self, info):
        id, roomID = info
        if not self.videoWidgetList[
                id - 9].isHidden() and roomID != '0' and roomID:  # 房间号有效
            self.videoWidgetList[id - 9].roomID = roomID
            self.videoWidgetList[id - 9].mediaReload()
            self.config['player'][id - 9] = roomID
            self.liverPanel.updatePlayingStatus(self.config['player'])
            self.dumpConfig.start()

    def keyPressEvent(self, QKeyEvent):
        if QKeyEvent.key() == Qt.Key_Escape or QKeyEvent.key() == Qt.Key_F:
            self.fullScreen()  # 自动判断全屏状态并退出
        elif QKeyEvent.key() == Qt.Key_H:
            self.openControlPanel()
        elif QKeyEvent.key() == Qt.Key_M or QKeyEvent.key() == Qt.Key_S:
            self.muteExcept()

    def addCoverToPlayer(self, info):  # 窗口 房号
        self.addMedia(info)
        self.videoWidgetList[info[0]].roomID = info[1]  # 修改房号
        self.videoWidgetList[info[0]].mediaReload()  # 重载视频

    def refreshPlayerStatus(self, refreshIDList):  # 刷新直播状态发生变化的播放器
        for videoWidget in self.videoWidgetList:
            for roomID in refreshIDList:
                if roomID == videoWidget.roomID:
                    videoWidget.mediaReload()
                    break