Skip to content

Commit 4b41dba

Browse files
Update the video player
Fixes some bugs when dragging to ealier positions.
1 parent f4649c2 commit 4b41dba

2 files changed

Lines changed: 269 additions & 31 deletions

File tree

network_video_player.py

Lines changed: 53 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import logging
88
import requests
99
import os
10-
import tempfile
10+
import tempfile, _thread
1111
from collections import deque
1212

1313
try:
@@ -30,7 +30,7 @@
3030
QTextEdit,
3131
QProgressDialog,
3232
)
33-
from PySide6.QtCore import Qt, QThread, Signal, QMutex, QTimer
33+
from PySide6.QtCore import Qt, QThread, Signal, QMutex
3434
from PySide6.QtGui import QImage, QPixmap, QPainter, QColor
3535

3636

@@ -91,7 +91,6 @@ def __init__(self, parent=None):
9191
self.audio_data = []
9292
self.max_samples = 100
9393
self.data_status = "等待音频数据..."
94-
self.isToSeek = False
9594

9695
def update_audio(self, data, data_size=0):
9796
self.data_status = f"数据大小: {data_size}字节"
@@ -167,6 +166,7 @@ def __init__(self, video_path):
167166
self.external_seek_request = False
168167
self.seek_position = 0
169168
self.key_frames = [] # 存储关键帧位置
169+
self._is_to_stop_old_frameTimer = False
170170

171171
def load_key_frames(self):
172172
"""预加载关键帧位置,提高定位准确性"""
@@ -224,6 +224,7 @@ def run(self):
224224
self.mutex.lock()
225225
self.running = True
226226
self.mutex.unlock()
227+
self._is_to_stop_old_frameTimer = True
227228

228229
try:
229230
# 明确使用FFmpeg后端打开视频
@@ -249,6 +250,8 @@ def run(self):
249250

250251
play_interval = 1.0 / self.fps
251252
last_play_time = time.time()
253+
frm = 0
254+
self._is_first_frame = True
252255

253256
while True:
254257
self.mutex.lock()
@@ -292,11 +295,19 @@ def run(self):
292295

293296
self.mutex.unlock()
294297

295-
current_time = time.time()
296-
elapsed = current_time - last_play_time
297-
if elapsed < play_interval:
298-
time.sleep(play_interval - elapsed)
299-
last_play_time = current_time
298+
# current_time = time.time()
299+
# elapsed = current_time - last_play_time
300+
# if elapsed < play_interval:
301+
# time.sleep(play_interval - elapsed)
302+
# last_play_time = current_time
303+
frm += 1
304+
if self._is_first_frame:
305+
self._is_to_stop_old_frameTimer = False
306+
_thread.start_new_thread(self._set_the_frame, tuple())
307+
self._is_first_frame = False
308+
else:
309+
while frm >= self._frm:
310+
time.sleep(0.00001)
300311

301312
ret, frame = self.cap.read()
302313
if not ret:
@@ -331,6 +342,19 @@ def run(self):
331342
self.running = False
332343
self.mutex.unlock()
333344

345+
def _set_the_frame(self):
346+
self._frm = 0
347+
while True:
348+
if self._is_to_stop_old_frameTimer:
349+
self._is_to_stop_old_frameTimer = False
350+
break
351+
if self.paused:
352+
time.sleep(0.00001)
353+
continue
354+
self._frm += 1
355+
time.sleep(1.0 / self.fps)
356+
# print(self._frm)
357+
334358
def _extract_audio_spec(self):
335359
if not has_ffmpeg:
336360
self.error_occurred.emit("未检测到ffmpeg,仅播放视频画面(无声音)")
@@ -442,11 +466,14 @@ def init_audio(self, audio_spec, video_path):
442466
self.pa = pyaudio.PyAudio()
443467

444468
try:
445-
self.debug_file = wave.open("audio_debug.wav", "wb")
446-
self.debug_file.setnchannels(audio_spec["channels"])
447-
self.debug_file.setsampwidth(2)
448-
self.debug_file.setframerate(audio_spec["sample_rate"])
449-
logging.info("调试音频文件初始化成功")
469+
if "--debug" in sys.argv:
470+
self.debug_file = wave.open("audio_debug.wav", "wb")
471+
self.debug_file.setnchannels(audio_spec["channels"])
472+
self.debug_file.setsampwidth(2)
473+
self.debug_file.setframerate(audio_spec["sample_rate"])
474+
logging.info("调试音频文件初始化成功")
475+
else:
476+
self.debug_file = None
450477
except Exception as e:
451478
self.audio_error.emit(f"调试文件创建失败: {str(e)}")
452479
logging.error(f"调试文件创建失败: {str(e)}")
@@ -812,8 +839,8 @@ def __init__(self, parent=None, debug=False):
812839
self.target_position = 0
813840
self.range_supported = True # 服务器是否支持Range请求
814841
self.reloading = False # 是否正在重新加载视频
815-
self.isToSeek = False
816-
self.seekTo = None
842+
self._is_to_play = False
843+
self._itp2 = False
817844

818845
self.init_ui()
819846

@@ -1041,12 +1068,11 @@ def _reload_and_seek(self, target_pos_ms):
10411068
# progress_dialog.close()
10421069
# self.reloading = False
10431070
# self.video_label.setText("")
1044-
self.stop()
1071+
# self.play_btn.click()
10451072
self.video_loaded = False
1046-
self.isToSeek = True
1047-
self.seekTo = target_pos_ms
1073+
self._to_play_dur = target_pos_ms
1074+
self._is_to_play = True
10481075
self.load_video(self.video_path)
1049-
# self.set_play_position(target_pos_ms)
10501076

10511077
def _on_audio_ready_after_reload(self, audio_spec, target_pos_ms):
10521078
"""重新加载后音频准备就绪的回调"""
@@ -1120,15 +1146,8 @@ def _start_video_loading(self):
11201146
self.video_thread.start()
11211147
self.video_loaded = True
11221148
self.log("视频加载完成,点击Play开始播放")
1123-
self._to = QTimer(self)
1124-
1125-
def tmp():
1126-
self._to.stop()
1127-
if self.isToSeek:
1128-
self.play_btn.click()
1129-
1130-
self._to.timeout.connect(tmp)
1131-
self._to.start(100)
1149+
if self._is_to_play:
1150+
self._itp2 = True
11321151
except Exception as e:
11331152
QMessageBox.critical(self, "加载错误", f"无法加载视频: {str(e)}")
11341153
self.log(f"加载错误: {str(e)}")
@@ -1188,9 +1207,12 @@ def on_volume_changed(self, value):
11881207
self.audio_thread.set_volume(value)
11891208

11901209
def update_frame(self, q_image, timestamp):
1191-
if self.isToSeek:
1192-
self.set_play_position(self.seekTo)
1193-
self.isToSeek = False
1210+
if self._itp2:
1211+
self._is_to_play = False
1212+
self._itp2 = False
1213+
self.set_play_position(self._to_play_dur)
1214+
self.play_btn.click()
1215+
self.play_btn.click()
11941216
if not self.is_dragging and not self.reloading:
11951217
scaled_img = q_image.scaled(
11961218
self.video_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation

network_video_player_old.py

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
import sys,os
2+
try:
3+
import vlc
4+
except Exception:
5+
from ctypes import cdll
6+
cdll.LoadLibrary(os.path.join(os.getcwd(),"libvlc.dll"))
7+
import vlc
8+
from PySide6.QtWidgets import (
9+
QApplication,
10+
QMainWindow,
11+
QWidget,
12+
QVBoxLayout,
13+
QPushButton,
14+
QLabel,
15+
QHBoxLayout,
16+
QTabWidget,
17+
)
18+
from PySide6.QtCore import Qt, QSize
19+
from PySide6.QtGui import QResizeEvent
20+
from PySide6 import QtCore, QtWidgets
21+
22+
23+
class VLCVideoWidget(QWidget):
24+
"""独立的VLC视频播放组件,可嵌入入任何Qt窗口"""
25+
26+
def __init__(self, parent=None):
27+
super().__init__(parent)
28+
self.instance = vlc.Instance(
29+
[
30+
"--no-xlib", # 避免X11相关问题
31+
"--no-video-title-show", # 禁用标题显示
32+
"--avcodec-skip-frame=0", # 不跳过任何帧
33+
"--avcodec-skip-idct=0", # 不跳过IDCT步骤
34+
]
35+
)
36+
if not self.instance:
37+
QtWidgets.QMessageBox.critical(
38+
self,
39+
self.tr("Can't load vlc player"),
40+
self.tr(
41+
"To preview video asset,you should install VLC Media Player at first."
42+
),
43+
)
44+
return
45+
self.media_player = self.instance.media_player_new()
46+
47+
# 初始化UI
48+
self.init_ui()
49+
50+
# 绑定VLC播放器到窗口
51+
self.bind_player_to_widget()
52+
53+
def init_ui(self):
54+
"""初始化播放控件UI"""
55+
# 视频显示区域
56+
self.video_frame = QWidget()
57+
self.video_frame.setStyleSheet("background-color: black;")
58+
self.video_frame.setMinimumSize(640, 360)
59+
60+
# 控制按钮
61+
self.play_btn = QPushButton("Play") # ("播放")
62+
self.play_btn.clicked.connect(self.toggle_play)
63+
self.stop_btn = QPushButton("Stop") # ("停止")
64+
self.stop_btn.clicked.connect(self.stop)
65+
66+
# 状态显示
67+
self.status_label = QLabel("Ready") # ("就绪")
68+
self.status_label.setAlignment(Qt.AlignCenter)
69+
70+
# 布局管理
71+
control_layout = QHBoxLayout()
72+
control_layout.addWidget(self.play_btn)
73+
control_layout.addWidget(self.stop_btn)
74+
75+
main_layout = QVBoxLayout(self)
76+
main_layout.addWidget(self.video_frame)
77+
main_layout.addLayout(control_layout)
78+
main_layout.addWidget(self.status_label)
79+
80+
self.setLayout(main_layout)
81+
82+
def bind_player_to_widget(self):
83+
"""将VLC播放器绑定到当前窗口部件"""
84+
if sys.platform.startswith("win"):
85+
self.media_player.set_hwnd(self.video_frame.winId())
86+
elif sys.platform.startswith("linux"):
87+
self.media_player.set_xwindow(self.video_frame.winId())
88+
elif sys.platform.startswith("darwin"):
89+
self.media_player.set_nsobject(int(self.video_frame.winId()))
90+
91+
def load_video(self, url):
92+
"""加载视频(支持本地文件路径或网络URL)"""
93+
if not self.instance:
94+
return
95+
if url.startswith(("http://", "https://", "rtsp://")):
96+
self.status_label.setText(
97+
f"Loaded video" #:{url.split('/')[-1]}"
98+
) # (f"加载网络视频: {url.split('/')[-1]}")
99+
media = self.instance.media_new(url)
100+
else:
101+
self.status_label.setText(
102+
f"Loaded video" #:{url.split('/')[-1]}"
103+
) # (f"加载本地视频: {url.split('/')[-1]}")
104+
media = self.instance.media_new(url)
105+
106+
self.media_player.set_media(media)
107+
media.parse() # 解析媒体信息
108+
109+
def toggle_play(self):
110+
"""切换播放/暂停状态"""
111+
if self.media_player.is_playing():
112+
self.media_player.pause()
113+
self.play_btn.setText("Play") # ("播放")
114+
self.status_label.setText("Paused") # ("已暂停")
115+
else:
116+
self.media_player.play()
117+
self.play_btn.setText("Pause") # ("暂停")
118+
self.status_label.setText("Playing...") # ("播放中...")
119+
120+
def stop(self):
121+
"""停止播放"""
122+
self.media_player.stop()
123+
self.play_btn.setText("Play") # ("播放")
124+
self.status_label.setText("Stopped") # ("已停止")
125+
126+
def resizeEvent(self, event: QResizeEvent):
127+
"""窗口大小改变时自适应调整视频"""
128+
if self.media_player:
129+
self.media_player.video_set_scale(0) # 0表示自适应窗口大小
130+
super().resizeEvent(event)
131+
132+
def closeEvent(self, event):
133+
"""关闭时释放资源"""
134+
self.media_player.stop()
135+
event.accept()
136+
137+
def getPlayProgress(self):
138+
"""获取播放进度"""
139+
return (self.media_player.get_time(), self.media_player.get_length())
140+
141+
def setPlayProgress(self, playprogress: int):
142+
"""设置播放进度"""
143+
current = self.media_player.get_time()
144+
total = self.media_player.get_length()
145+
146+
if playprogress < current:
147+
# 用停止法了事得了
148+
self.media_player.stop()
149+
self.media_player.play()
150+
self.media_player.set_time(playprogress)
151+
else:
152+
# 向后拖动直接设置
153+
self.media_player.set_time(playprogress)
154+
155+
# 恢复播放
156+
if self.media_player.is_playing():
157+
self.media_player.play()
158+
159+
160+
# 示例:在不同窗口中引用视频组件
161+
class MainWindow(QMainWindow):
162+
def __init__(self):
163+
super().__init__()
164+
self.setWindowTitle("视频组件复用示例")
165+
self.setGeometry(100, 100, 1280, 720)
166+
167+
# 创建标签页演示多窗口复用
168+
self.tabs = QTabWidget()
169+
170+
# 第一个标签页:播放测试视频
171+
self.tab1 = QWidget()
172+
self.tab1_layout = QVBoxLayout(self.tab1)
173+
self.video_player1 = VLCVideoWidget()
174+
self.video_player1.load_video(
175+
"https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/720/Big_Buck_Bunny_720_10s_1MB.mp4"
176+
)
177+
self.tab1_layout.addWidget(self.video_player1)
178+
179+
# 第二个标签页:预留的另一个播放器
180+
self.tab2 = QWidget()
181+
self.tab2_layout = QVBoxLayout(self.tab2)
182+
self.video_player2 = VLCVideoWidget()
183+
self.tab2_layout.addWidget(self.video_player2)
184+
self.tab2_layout.addWidget(QLabel("在此处可加载其他视频"))
185+
186+
# 添加标签页
187+
self.tabs.addTab(self.tab1, "测试视频")
188+
self.tabs.addTab(self.tab2, "备用播放器")
189+
190+
self.setCentralWidget(self.tabs)
191+
192+
193+
class SecondWindow(QMainWindow):
194+
"""另一个窗口,同样可以引用视频组件"""
195+
196+
def __init__(self):
197+
super().__init__()
198+
self.setWindowTitle("第二个窗口中的视频播放器")
199+
self.setGeometry(200, 200, 960, 540)
200+
201+
self.video_player = VLCVideoWidget()
202+
self.setCentralWidget(self.video_player)
203+
204+
205+
if __name__ == "__main__":
206+
app = QApplication(sys.argv)
207+
208+
# 显示主窗口
209+
main_window = MainWindow()
210+
main_window.show()
211+
212+
# 显示第二个窗口(演示多窗口复用)
213+
second_window = SecondWindow()
214+
second_window.show()
215+
216+
sys.exit(app.exec())

0 commit comments

Comments
 (0)