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/点歌

相关推荐
前端爆冲10 分钟前
项目中无用export的检测方案
前端
热爱编程的小曾37 分钟前
sqli-labs靶场 less 8
前端·数据库·less
gongzemin1 小时前
React 和 Vue3 在事件传递的区别
前端·vue.js·react.js
Apifox1 小时前
如何在 Apifox 中通过 Runner 运行包含云端数据库连接配置的测试场景
前端·后端·ci/cd
树上有只程序猿1 小时前
后端思维之高并发处理方案
前端
鸿蒙布道师2 小时前
鸿蒙NEXT开发对象工具类(TS)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
庸俗今天不摸鱼2 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
黄毛火烧雪下2 小时前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox2 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞2 小时前
Firefox默认在新标签页打开收藏栏链接
前端·firefox