VisionPro开发 - 利用视频材质实现一台复古小电视


VisionPro开发系列首页:漫游Apple Vision Pro

系列项目全部代码:github.com/xuchi16/vis...


本文主要包含以下内容:

  • 视频材质(Video Material)
  • 控制视频材质的播放和暂停
  • 获取 USDZ 模型的子实体

最终成果:一台支持播放/暂停功能的复古小电视

基本概念

在 RealityKit 中,材质(Material)是一种用于定义 3D 物体表面渲染效果的对象。视频材质是一种支持动画纹理的材质,可以把视频映射到物体表面。视频材质是一种无光照(unlit)类型,意味着场景中的光照不会对它造成任何影响。同时,如果源视频文件的格式支持透明度,视频材质也支持透明度调整。

视频材质使用AVPlayer实例来控制视频播放,因此我们可以使用任何AVPlayer支持的视频格式。还可以利用其play()pause()方法等控制视频播放和暂停。

主要步骤

  1. 创建视频播放控制和状态管理的模型(Model)
  2. 导入实体,并获取需要展示为视频材质的子实体
  3. 创建视频材质,并添加到子实体上
  4. 创建控制视频播放的视图
  5. 将播放试图通过附件(attachment) 附加到实体上

基本实现

视频播放和状态管理

首先,我们需要创建一个管理视频播放状态的模型,其中包含播放器和播放内容。当应用启动时,初始化该 Model,并且传递给不同的 View。

swift 复制代码
class PlayModel: ObservableObject {
    var player = AVPlayer()
    var item: AVPlayerItem?
    
    func load(_ url: URL) {
        item = AVPlayerItem(url: url)
        player.replaceCurrentItem(with: item)
    }
}

实体导入及子实体获取

将电视的 USDZ 文件添加到 Packages 中,并在 ImmersiveView 中加载该模型。这一模型包含两个部分:电视的主体和屏幕。可以通过 Entity 类型的findEntity(named:)方法找到屏幕对应的子实体。

swift 复制代码
RealityView { content, attachments in 
    let tv = try! await Entity(named: "tv_retro")
    if let screen = tv.findEntity(named: "tv_retro_2_RetroTVScreen") as? ModelEntity {
        // 获取到了屏幕实体
    }
    content.add(tv)
}

创建视频材质

在 ImmersiveView 中从 Model 中获取到播放器,添加到视频材质上,并绑定到屏幕实体上。并且当实体加载完成后播放视频。

swift 复制代码
var playModel: PlayModel

RealityView { content in
    let tv = try! await Entity(named: "tv_retro")
    if let screen = tv.findEntity(named: "tv_retro_2_RetroTVScreen") as? ModelEntity {
        let player = playModel.player
        let material = VideoMaterial(avPlayer: player)
        screen.model?.materials = [material]
        player.play()
    }
    content.add(tv)
}
.onAppear() {
    if let url = Bundle.main.url(forResource: "GetReadyRemixRB", withExtension: "mp4") {
        playModel.load(url)
    }
}

创建视频播放控制视图

播放控制试图中只包含一个按钮:

  • 当视频为暂停状态时,为播放按钮,点击后即可播放
  • 当视频为播放状态时,为暂停按钮,点击后暂停视频

因此需要对PlayModel进行增强,增加对应视频播放状态判断以及播放控制的功能。其中,togglePlayPause()用于在不同状态下对视频进行播放和暂停操作。observePlayer()用于监听视频的播放状态。

swift 复制代码
class PlayModel: ObservableObject {
    var player = AVPlayer()
    var item: AVPlayerItem?
    
    private var subscriptions = Set<AnyCancellable>()
    @Published var isPlaying: Bool = false
    
    init() {
        observePlayer()
    }
    
    // ...
    
    func togglePlayPause() {
        if player.rate == 0 {
            player.play()
        } else {
            player.pause()
        }
    }
    
    private func observePlayer() {
        player.publisher(for: .timeControlStatus)
            .receive(on: RunLoop.main)
            .sink(receiveValue: { [weak self] status in
                switch status {
                case .playing:
                    self?.isPlaying = true
                default:
                    self?.isPlaying = false
                }
            })
            .store(in: &subscriptions)
    }
}

在此基础上,新增一个 ControllerView,用于展示播放控制按钮。

  • 图标根据视频状态不同,分别用pause.fillplay.fill表示
  • 按钮行为通过调用 Model 中的togglePlayPause()函数实现控制
swift 复制代码
struct ControllerView: View {
    @ObservedObject var playModel = PlayModel()
    
    var body: some View {
        Button(action: {
            playModel.togglePlayPause()
        }) {
            Image(systemName: playModel.isPlaying ? "pause.fill" : "play.fill")
                .font(.system(size: 40))
                .accessibilityLabel(playModel.isPlaying ? "Pause" : "Play")
                .padding(.all, 40)
        }
        .padding(12)
    }
    
}

为实体添加附件

回到 ImmversiveView 中,在电视实体下方添加上述新建的 ControllerView。我们为新建一个 entity 用于容纳 tv 实体及其附件,并且将它们都添加到 entity 中即可。

swift 复制代码
@State var entity = Entity()

RealityView { content, attachments in
    // load tv
    entity.addChild(tv)
    if let attachment = attachments.entity(for: "controller") {
        attachment.position = [0, 0.98, -1.2]
        entity.addChild(attachment)
    }
    content.add(entity)
} attachments: {
    Attachment(id: "controller") {
        ControllerView(playModel: playModel)
    }
}

最终效果

关注我

欢迎在掘金上关注我和我的专栏VisionOS Workshop,以及各种收藏/围观/评论/反馈/批评/Star/点歌

相关推荐
燃先生._.23 分钟前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖1 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235241 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240252 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar2 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人3 小时前
前端知识补充—CSS
前端·css
GISer_Jing3 小时前
2025前端面试热门题目——计算机网络篇
前端·计算机网络·面试
m0_748245523 小时前
吉利前端、AI面试
前端·面试·职场和发展
理想不理想v3 小时前
webpack最基础的配置
前端·webpack·node.js