因为近期工作需要使用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,你就获得了自己的弹窗广告啦~


457

被折叠的 条评论
为什么被折叠?



