1
0
forked from Rowland/EG
EG/demo/video_player_example.py
2025-07-02 09:49:59 +08:00

395 lines
14 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Panda3D 视频播放示例
演示如何在Panda3D中播放内嵌视频
"""
from direct.showbase.ShowBase import ShowBase
from panda3d.core import (
MovieTexture, CardMaker, TextureStage, NodePath,
WindowProperties, loadPrcFileData, Vec3, Point3,
AmbientLight, DirectionalLight, GeomNode
)
import os
# 配置Panda3D
loadPrcFileData("", """
win-size 1280 720
window-title Panda3D 视频播放器
show-frame-rate-meter true
""")
class VideoPlayerDemo(ShowBase):
def __init__(self):
ShowBase.__init__(self)
# 设置背景色
self.setBackgroundColor(0.1, 0.1, 0.1)
# 设置相机
self.cam.setPos(0, -10, 0)
self.cam.lookAt(0, 0, 0)
# 添加光照
self.setup_lighting()
# 创建视频播放器
self.setup_video_players()
# 设置控制
self.setup_controls()
print("视频播放器已启动!")
print("控制键:")
print(" SPACE - 播放/暂停")
print(" R - 重新播放")
print(" + - 加速播放")
print(" - - 减速播放")
print(" ESC - 退出")
def setup_lighting(self):
"""设置基础光照"""
# 环境光
alight = AmbientLight('ambient')
alight.setColor((0.3, 0.3, 0.3, 1))
alnp = self.render.attachNewNode(alight)
self.render.setLight(alnp)
# 定向光
dlight = DirectionalLight('directional')
dlight.setColor((0.8, 0.8, 0.8, 1))
dlnp = self.render.attachNewNode(dlight)
dlnp.setHpr(45, -45, 0)
self.render.setLight(dlnp)
def setup_video_players(self):
"""设置视频播放器"""
self.videos = []
# 方法1: 平面视频播放器
self.create_flat_video_player()
# 方法2: 3D模型上的视频纹理
self.create_3d_video_screen()
# 方法3: 球形全景视频
self.create_spherical_video()
def create_flat_video_player(self):
"""创建平面视频播放器"""
try:
# 创建视频纹理 (这里使用示例路径,需要替换为实际视频文件)
video_path = "video/sample.mp4" # 支持 .mp4, .avi, .mov 等格式
# 如果文件不存在,创建一个简单的色彩动画作为演示
if not os.path.exists(video_path):
self.create_color_animation_demo()
return
# 加载视频
movie_texture = MovieTexture()
success = movie_texture.read(video_path)
if success:
# 创建卡片几何体
cm = CardMaker("video_card")
cm.setFrame(-2, 2, -1.5, 1.5)
video_card = self.render.attachNewNode(cm.generate())
video_card.setTexture(movie_texture)
video_card.setPos(-4, 0, 0)
# 播放视频
movie_texture.play()
self.videos.append({
'texture': movie_texture,
'node': video_card,
'name': 'flat_video'
})
print(f"✓ 平面视频播放器创建成功: {video_path}")
else:
print(f"✗ 无法加载视频: {video_path}")
except Exception as e:
print(f"创建平面视频播放器失败: {str(e)}")
def create_3d_video_screen(self):
"""在3D模型上创建视频纹理"""
try:
# 创建一个简单的立方体作为"电视屏幕"
from panda3d.core import CardMaker
# 创建屏幕边框
frame_cm = CardMaker("screen_frame")
frame_cm.setFrame(-2.2, 2.2, -1.7, 1.7)
screen_frame = self.render.attachNewNode(frame_cm.generate())
screen_frame.setColor(0.1, 0.1, 0.1, 1)
screen_frame.setPos(0, 0, 0)
# 创建屏幕
screen_cm = CardMaker("video_screen")
screen_cm.setFrame(-2, 2, -1.5, 1.5)
video_screen = screen_frame.attachNewNode(screen_cm.generate())
# 创建程序化视频内容 (颜色渐变动画)
movie_texture = self.create_procedural_video(panoramic=False)
video_screen.setTexture(movie_texture)
self.videos.append({
'texture': movie_texture,
'node': video_screen,
'name': '3d_screen'
})
print("✓ 3D视频屏幕创建成功")
except Exception as e:
print(f"创建3D视频屏幕失败: {str(e)}")
def create_spherical_video(self):
"""创建球形全景视频"""
try:
# 创建球体几何(程序化生成)
sphere = self.create_sphere_geometry(radius=2)
sphere.reparentTo(self.render)
sphere.setPos(4, 0, 0)
sphere.setScale(2)
# 反转法线以便从内部观看
sphere.setTwoSided(True)
# 创建全景视频纹理
panoramic_texture = self.create_procedural_video(panoramic=True)
sphere.setTexture(panoramic_texture)
self.videos.append({
'texture': panoramic_texture,
'node': sphere,
'name': 'spherical_video'
})
print("✓ 球形全景视频创建成功")
except Exception as e:
print(f"创建球形全景视频失败: {str(e)}")
def create_sphere_geometry(self, radius=1):
"""程序化创建球体几何"""
# 创建简化的球体立方体的6个面
sphere_root = self.render.attachNewNode("sphere")
faces = [
# (位置, 旋转)
(Vec3(0, radius, 0), Vec3(0, 0, 0)), # 前面
(Vec3(0, -radius, 0), Vec3(0, 180, 0)), # 后面
(Vec3(radius, 0, 0), Vec3(0, 90, 0)), # 右面
(Vec3(-radius, 0, 0), Vec3(0, -90, 0)), # 左面
(Vec3(0, 0, radius), Vec3(-90, 0, 0)), # 上面
(Vec3(0, 0, -radius), Vec3(90, 0, 0)), # 下面
]
cm = CardMaker("sphere_face")
cm.setFrame(-radius, radius, -radius, radius)
for i, (pos, hpr) in enumerate(faces):
face = sphere_root.attachNewNode(cm.generate())
face.setPos(pos)
face.setHpr(hpr)
return sphere_root
def create_procedural_video(self, panoramic=False):
"""创建程序化视频内容作为演示"""
from panda3d.core import PNMImage, Texture
# 创建动态纹理
texture = Texture()
if panoramic:
texture.setup2dTexture(512, 256, Texture.TUnsignedByte, Texture.FRgba)
else:
texture.setup2dTexture(256, 256, Texture.TUnsignedByte, Texture.FRgba)
# 启动更新任务 - 修复参数传递使用lambda
task_name = f"update_video_{'panoramic' if panoramic else 'normal'}"
self.taskMgr.add(lambda task: self.update_procedural_video(task, texture, panoramic), task_name)
return texture
def update_procedural_video(self, task, texture, panoramic):
"""更新程序化视频内容"""
try:
from panda3d.core import PNMImage
import math
# 获取当前时间
time = task.time
# 根据纹理类型创建不同尺寸的图像
if panoramic:
img = PNMImage(512, 256, 4) # 全景纹理
width, height = 512, 256
else:
img = PNMImage(256, 256, 4) # 普通纹理
width, height = 256, 256
for y in range(height):
for x in range(width):
# 创建动态色彩效果
if panoramic:
# 全景模式:创建旋转的渐变
u = x / float(width) # 经度 (0-1)
v = y / float(height) # 纬度 (0-1)
angle = u * 2 * math.pi + time * 0.5
height_factor = (v - 0.5) * math.pi
r = (math.sin(angle * 3) + 1) * 0.5
g = (math.cos(angle * 2 + height_factor * 4) + 1) * 0.5
b = (math.sin(height_factor * 6 + time) + 1) * 0.5
else:
# 普通模式:创建波纹效果
center_x = width // 2
center_y = height // 2
dist_x = (x - center_x) / float(center_x)
dist_y = (y - center_y) / float(center_y)
distance = math.sqrt(dist_x**2 + dist_y**2)
wave = math.sin(distance * 10 - time * 5)
r = (wave + 1) * 0.5
g = (math.sin(dist_x * 5 + time * 2) + 1) * 0.5
b = (math.sin(dist_y * 5 + time * 3) + 1) * 0.5
img.setXel(x, y, r, g, b)
img.setAlpha(x, y, 1.0)
# 更新纹理
texture.load(img)
return task.cont
except Exception as e:
print(f"更新程序化视频失败: {str(e)}")
return task.done
def create_color_animation_demo(self):
"""创建颜色动画演示(当没有视频文件时)"""
try:
# 创建卡片
cm = CardMaker("color_demo")
cm.setFrame(-2, 2, -1.5, 1.5)
demo_card = self.render.attachNewNode(cm.generate())
demo_card.setPos(-4, 0, 0)
# 创建动态颜色纹理
demo_texture = self.create_procedural_video(panoramic=False)
demo_card.setTexture(demo_texture)
self.videos.append({
'texture': demo_texture,
'node': demo_card,
'name': 'color_demo'
})
print("✓ 颜色动画演示创建成功")
except Exception as e:
print(f"创建颜色动画演示失败: {str(e)}")
def setup_controls(self):
"""设置控制键"""
self.accept("space", self.toggle_play_pause)
self.accept("r", self.restart_videos)
self.accept("plus", self.speed_up)
self.accept("minus", self.slow_down)
self.accept("escape", self.quit_app)
# 鼠标控制相机
self.accept("mouse1", self.start_camera_rotation)
self.accept("mouse1-up", self.stop_camera_rotation)
self.camera_rotating = False
self.last_mouse_x = 0
self.last_mouse_y = 0
# 启动相机更新任务
self.taskMgr.add(self.update_camera_rotation, "camera_rotation")
def toggle_play_pause(self):
"""切换播放/暂停"""
for video in self.videos:
if hasattr(video['texture'], 'isPlaying'):
if video['texture'].isPlaying():
video['texture'].stop()
print(f"暂停视频: {video['name']}")
else:
video['texture'].play()
print(f"播放视频: {video['name']}")
def restart_videos(self):
"""重新播放所有视频"""
for video in self.videos:
if hasattr(video['texture'], 'play'):
video['texture'].setTime(0)
video['texture'].play()
print(f"重新播放视频: {video['name']}")
def speed_up(self):
"""加速播放"""
for video in self.videos:
if hasattr(video['texture'], 'getPlayRate'):
current_rate = video['texture'].getPlayRate()
new_rate = min(current_rate * 1.5, 4.0)
video['texture'].setPlayRate(new_rate)
print(f"加速播放 {video['name']}: {new_rate:.2f}x")
def slow_down(self):
"""减速播放"""
for video in self.videos:
if hasattr(video['texture'], 'getPlayRate'):
current_rate = video['texture'].getPlayRate()
new_rate = max(current_rate / 1.5, 0.25)
video['texture'].setPlayRate(new_rate)
print(f"减速播放 {video['name']}: {new_rate:.2f}x")
def start_camera_rotation(self):
"""开始相机旋转"""
if self.mouseWatcherNode.hasMouse():
self.camera_rotating = True
self.last_mouse_x = self.mouseWatcherNode.getMouseX()
self.last_mouse_y = self.mouseWatcherNode.getMouseY()
def stop_camera_rotation(self):
"""停止相机旋转"""
self.camera_rotating = False
def update_camera_rotation(self, task):
"""更新相机旋转"""
if self.camera_rotating and self.mouseWatcherNode.hasMouse():
mouse_x = self.mouseWatcherNode.getMouseX()
mouse_y = self.mouseWatcherNode.getMouseY()
delta_x = mouse_x - self.last_mouse_x
delta_y = mouse_y - self.last_mouse_y
# 更新相机方向
self.cam.setH(self.cam.getH() - delta_x * 50)
new_p = self.cam.getP() + delta_y * 50
self.cam.setP(max(min(new_p, 89), -89))
self.last_mouse_x = mouse_x
self.last_mouse_y = mouse_y
return task.cont
def quit_app(self):
"""退出应用"""
print("退出视频播放器...")
self.userExit()
if __name__ == "__main__":
app = VideoPlayerDemo()
app.run()