【PyQt+vlc】从环境配置到手搓一个摸鱼广告弹窗

该文章已生成可运行项目,

因为近期工作需要使用pyqt,又受到某短视频启发,实战练习一下。本项目是一个基于 PyQt5 和 VLC 媒体库开发的本地视频播放器,伪装成了悬浮透明的广告弹窗,让你可以尽情摸鱼看视频不被发现,有人来就可以假装是广告关掉(小窗隐藏),可以支持包括 MP4、AVI、MOV、MKV 等主流视频格式,提供了不同播放模式选择,音量、播放速度、进度调节,播放上下集,快捷键隐藏暂停等功能。

文章目录

  • 一、环境配置
  • 二、VLC安装
  • 三、注意点
  • 四、手搓摸鱼广告弹窗
    • 1. 类结构设计
    • 2. 窗口样式与布局
    • 3. 背景广告图片设置
    • 4. 布局管理与组件层次
    • 5. VLC 媒体
    • 6. 事件驱动的播放控制
    • 7. 不同播放模式功能
    • 8. 进度条与拖拽控制
    • 9. 动态背景功能
    • 10. 交互式控件设计
    • 11. 文件管理与批量加载
    • 12. 快捷键功能
  • 五、效果展示

一、环境配置

广告弹窗主要用到两个库:vlc 用于视频播放,PyQt5 用于创建窗口和控件。又是最重要的版本问题!血与泪的教训!一律尽量用conda安装,不然重装到你泪流满面!

conda install python=3.10.9 pyqt=5.15.7 pyqtwebengine=5.15.7

二、VLC安装

这是windows的:
https://get.videolan.org/vlc/3.0.21/win64/vlc-3.0.21-win64.exe
安装好 VLC 本体后,再安装 Python 绑定库:

pip install python-vlc

后续封装时用到的.zip文件下载网址:
https://get.videolan.org/vlc/3.0.21/win64/vlc-3.0.21-win64.zip
http://ftp.halifax.rwth-aachen.de/videolan/vlc/3.0.21/win64/

三、注意点

写这个代码的时候有一个注意点,要避免线程搞乱了,尤其是在写抓取视频结束自动播放的时候。
PyQt 的所有 UI 相关操作(如按钮点击、窗口刷新、输入响应等)必须在主线程执行。如果在子线程里执行,就不会显示,也不会报错,只是静默失败。

四、手搓摸鱼广告弹窗

code is cheap, show me the prompt.近期最佳金句哈哈哈哈。
代码一共12个核心提示,下面依次讲解:

1. 类结构设计

主类 LoopingVideoPlayer 继承自 QWidget,封装了视频播放器的所有功能。构造函数中初始化了窗口尺寸、播放状态、循环模式等核心属性。

class LoopingVideoPlayer(QWidget):
    def __init__(self, background_image_path=None):
        super().__init__()
        # 窗口属性初始化
        self.allwidth = 480
        self.allhight = int(self.allwidth / 16 * 9)
        self.shuffle = False
        
        # 播放列表管理
        self.video_files = []
        self.current_index = 0
        self.is_media_loaded = False
        
        # 循环模式:0=不循环,1=单曲循环,2=列表循环,3=随机播放,4=单曲播放停止
        self.loop_mode = 0

        self.background_image_path = background_image_path

2. 窗口样式与布局

播放器采用了现代化的悬浮窗口设计:

  • 无边框设计: 使用 Qt.FramelessWindowHint 移除系统标题栏
  • 置顶显示: Qt.WindowStaysOnTopHint 确保窗口始终在最前
  • 半透明效果: setWindowOpacity(0.8) 设置 80% 透明度
  • 智能定位: 自动定位到屏幕右下角
# 设置无边框、总在最上层、半透明
self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint)
self.setWindowOpacity(0.8)
self.setAttribute(Qt.WA_TranslucentBackground, True)

# 获取屏幕尺寸并设置窗口位置到右下角
screen = QApplication.primaryScreen()
screen_geometry = screen.geometry()
x = screen_geometry.width() - window_width - 50
y = screen_geometry.height() - window_height - 100
self.move(x, y)

3. 背景广告图片设置

背景图片系统是弹窗广告视觉效果的重要组成部分,支持单张图片和多张图片轮播两种模式。可以在图片右上角画一个×,再增加一个隐藏按钮,就可以得到虚假小窗关闭功能,等人走了再打开继续看。

def createBackgroundImage(self):
    """创建背景图片标签"""
    self.backgroundLabel = QLabel()
    self.backgroundLabel.setAlignment(Qt.AlignCenter)
    self.backgroundLabel.setStyleSheet("background-color: transparent;")
    self.backgroundLabel.setScaledContents(True)  # 允许图片缩放

    pixmap = None  # 默认没有加载到图片

    if isinstance(self.background_image_path, list):
        # 如果是 list,先判断非空再取第一个有效的
        if self.background_image_path:
            first_path = self.background_image_path[0]
            if first_path and os.path.exists(first_path):
                pixmap = QPixmap(first_path)
    else:
        # 如果是单个字符串
        if self.background_image_path and os.path.exists(self.background_image_path):
            pixmap = QPixmap(self.background_image_path)

    if pixmap and not pixmap.isNull():
        # 计算缩放后的尺寸,保持宽高比
        original_width = pixmap.width()
        original_height = pixmap.height()
        target_width = self.allwidth
        target_height = int((original_height / original_width) * target_width)
        self.backgroundLabel.setFixedSize(target_width, target_height)
        self.backgroundLabel.setPixmap(pixmap)
    else:
        # 如果没有图片,设置默认高度
        self.backgroundLabel.setFixedSize(self.allwidth, 180)
        
    # 最右上方添加一个隐藏按钮
    self.createToprightControls()

4. 布局管理与组件层次

播放器采用垂直布局管理,从上到下依次为:背景图片 → 顶部控制栏 → 视频播放区域 → 底部控制栏

# 先创建背景图片来计算实际高度
self.createBackgroundImage()

# 初始化VLC播放器
self.initVLCPlayer()

# 创建顶部控制栏
self.createTopControls()

# 创建底部控制栏
self.createBottomControls()

# 窗口整体布局
layout = QVBoxLayout()
layout.addWidget(self.backgroundLabel)      # 背景图片层
layout.addLayout(self.topLayout)            # 顶部控制栏
layout.addWidget(self.videoFrame)           # 视频播放区域
layout.addLayout(self.bottomLayout)         # 底部控制栏
layout.setContentsMargins(0, 0, 0, 0)       # 无外边框
layout.setSpacing(5)                        # 组件间距5像素
self.setLayout(layout)

# 动态窗口尺寸计算
bg_height = self.backgroundLabel.height()
window_height = bg_height + self.allhight + 80  # 背景+视频+控制栏+边距
self.resize(self.allwidth, window_height)

5. VLC 媒体

引擎初始化
VLC 初始化采用了双播放器架构,用于部署自动播放下集功能:

  • 媒体播放器 (media_player): 负责单个视频的播放控制
  • 列表播放器 (media_list_player): 管理播放列表和自动切换
def initVLCPlayer(self):
    # 释放旧的播放器实例
    if hasattr(self, 'media_player'):
        self.media_player.stop()
        self.media_player.release()
    
    # 初始化 VLC 实例
    self.vlc_instance = vlc.Instance()
    self.media_list_player = self.vlc_instance.media_list_player_new()
    self.media_player = self.vlc_instance.media_player_new()
    
    # 关联播放器
    self.media_list_player.set_media_player(self.media_player)

6. 事件驱动的播放控制

采用事件驱动模式处理播放状态变化,主要事件包括:

  • 位置变化: 更新进度条显示
  • 时长变化: 获取媒体总时长
  • 播放结束: 触发下一曲或循环播放逻辑
# 获取事件管理器并绑定回调
self.event_manager = self.media_player.event_manager()

# 核心事件绑定
self.event_manager.event_attach(vlc.EventType.MediaPlayerPositionChanged, self.on_position_changed)
self.event_manager.event_attach(vlc.EventType.MediaPlayerTimeChanged, self.on_time_changed)
self.event_manager.event_attach(vlc.EventType.MediaPlayerLengthChanged, self.on_length_changed)
self.event_manager.event_attach(vlc.EventType.MediaPlayerEndReached, self.on_media_end)

7. 不同播放模式功能

实现了五种播放模式的智能切换:

  • 模式 0: 顺序播放,播放完毕后停止
  • 模式 1: 单曲循环播放
  • 模式 2: 列表循环播放
  • 模式 3: 随机播放(会自动打乱播放列表)
  • 模式 4: 单曲播放后停止
def on_media_end(self, event):
    print(f"当前播放模式: {self.loop_mode}, 当前索引: {self.current_index}")
    
    if self.loop_mode == 0:  # 顺序播放
        if self.current_index < len(self.video_files) - 1:
            self.playNext()
        else:
            self.resetToInitialState()
    elif self.loop_mode == 1:  # 单曲循环
        self.playCurrentVideo()
    elif self.loop_mode == 2:  # 列表循环
        self.playNext()
    elif self.loop_mode == 3:  # 随机播放
        self.playNext()
    elif self.loop_mode == 4:  # 单曲播放停止
        self.resetToInitialState()

8. 进度条与拖拽控制

进度条控制采用多重信号绑定,确保用户操作的流畅性:

  • 使用标志位 seeking 和 slider_pressed 避免递归更新
  • 支持拖拽和点击两种交互方式
  • 实时位置同步和精确跳转
def seekVideo(self, position):
    """跳转到指定位置"""
    if hasattr(self, 'media_player') and self.media_player.get_length() > 0:
        self.seeking = True
        target_position = position / 1000.0  # 转换为 0.0-1.0 范围
        self.media_player.set_position(target_position)
        self.seeking = False

# 进度条事件绑定
self.progressSlider.sliderMoved.connect(self.seekVideo)
self.progressSlider.sliderPressed.connect(self.onSliderPressed)
self.progressSlider.sliderReleased.connect(self.onSliderReleased)

9. 动态背景功能

支持动态背景变换,从而实现更加逼真的弹窗广告效果:

  • 可配置背景图片列表
  • 定时自动切换(500ms 间隔)
  • 自适应缩放保持宽高比
   # 定时更改背景
   self.background_index = 0
   if isinstance(self.background_image_path, list) and self.background_image_path:
       self.bg_timer = QTimer()
       self.bg_timer.timeout.connect(self.updateBackgroundImage)
       self.bg_timer.start(500) # 1000 是 1s变一次
       
def updateBackgroundImage(self):
    """定时更新背景图片"""
    if isinstance(self.background_image_path, list) and self.background_image_path:
        path = self.background_image_path[self.background_index % len(self.background_image_path)]
        self.background_index += 1
        
        if path and os.path.exists(path):
            pixmap = QPixmap(path)
            if not pixmap.isNull():
                # 计算缩放后的尺寸
                original_width = pixmap.width()
                original_height = pixmap.height()
                target_width = self.allwidth
                target_height = int((original_height / original_width) * target_width)
                
                self.backgroundLabel.setFixedSize(target_width, target_height)
                self.backgroundLabel.setPixmap(pixmap)

10. 交互式控件设计

  • background-color:设置背景颜色
  • borde:边框
  • border-radius:圆角半径 ,让控件四角变圆润
  • color:字体颜色
  • font-weight:字体加粗
  • font-family:设置字体族
  • font-size:字号
def createBottomControls(self):
    """创建底部控制栏"""
        # 开始/暂停
        self.bottomLayout = QHBoxLayout()
        self.toggleButton = QPushButton('▶')
        self.toggleButton.setFixedSize(40, 25)
        self.toggleButton.clicked.connect(self.togglePlayback)
        self.toggleButton.setStyleSheet("""
            QPushButton {
                background-color: rgba(100, 100, 100, 0.4);
                border: none;
                border-radius: 10px;
                font-size: 20px;
                padding: 5px 10px;
                
            }
            QPushButton:hover {
                background-color: rgba(255, 255, 255, 0.8);
            }
        """)

def createToprightControls(self):
    self.hideButton = QPushButton('⬜', self) # ⬜\🛈\✖
    self.hideButton.setToolTip("隐藏/显示窗口")
    self.hideButton.setFixedSize(30, 30)

    # 设置按钮样式:半透明背景、无边框、圆角
    self.hideButton.setStyleSheet("""
        QPushButton {
            background-color: transparent;
            color: transparent;
            border: 0px solid transparent;
            outline: none;
            border-radius: 12px; 
            font-size: 30px; 
            font-family: "Segoe UI Symbol";
        }
    """)

11. 文件管理与批量加载

智能文件加载策略:

  • 格式支持: 支持主流视频格式
  • 目录扫描: 自动加载选中文件所在目录的所有视频
  • 去重排序: 使用 set 去重,然后排序
  • 路径标准化: 统一使用绝对路径
def loadVideoFiles(self, file_paths):
    """批量加载视频文件"""
    supported_extensions = ('.mp4', '.avi', '.mov', '.mkv', '.wmv', '.flv', '.webm', '.m4v', '.3gp', '.ts', '.mpg', '.mpeg')
    all_videos = set()

    for file_path in file_paths:
        if not os.path.exists(file_path):
            continue
            
        folder = os.path.dirname(file_path)
        # 扫描整个目录
        for filename in os.listdir(folder):
            full_path = os.path.join(folder, filename)
            if os.path.isfile(full_path) and filename.lower().endswith(supported_extensions):
                all_videos.add(os.path.abspath(full_path))

    self.video_files = sorted(all_videos)

12. 快捷键功能

实现了常用快捷键:

  • Ctrl+W: 最小化窗口
  • Ctrl+Q: 退出程序
  • Ctrl+E: 播放/暂停切换
# 快捷键绑定
QShortcut(QKeySequence("Ctrl+W"), self,
          activated=lambda: self.showMinimized())
QShortcut(QKeySequence("Ctrl+Q"), self, 
          activated=lambda: self.close())
QShortcut(QKeySequence("Ctrl+E"), self,
          activated=lambda: self.togglePlayback())

五、效果展示

将这篇东西发给claude,你就获得了自己的弹窗广告啦~
在这里插入图片描述

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值