[英]How do I create a looping video material in SceneKit for iOS app?
如何在 SceneKit 中创建播放循环视频的材质?
使用SpriteKit场景作为几何体的材质,可以在SceneKit中实现此目的。
以下示例将创建SpriteKit场景,使用视频播放器向其添加视频节点,制作视频播放器循环,创建SceneKit场景,添加SceneKit平面,最后将SpriteKit场景添加为平面的漫反射材质。
import UIKit
import SceneKit
import SpriteKit
import AVFoundation
class ViewController: UIViewController, SCNSceneRendererDelegate {
@IBOutlet weak var sceneView: SCNView!
override func viewDidLoad() {
super.viewDidLoad()
// A SpriteKit scene to contain the SpriteKit video node
let spriteKitScene = SKScene(size: CGSize(width: sceneView.frame.width, height: sceneView.frame.height))
spriteKitScene.scaleMode = .aspectFit
// Create a video player, which will be responsible for the playback of the video material
let videoUrl = Bundle.main.url(forResource: "videos/video", withExtension: "mp4")!
let videoPlayer = AVPlayer(url: videoUrl)
// To make the video loop
videoPlayer.actionAtItemEnd = .none
NotificationCenter.default.addObserver(
self,
selector: #selector(ViewController.playerItemDidReachEnd),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
object: videoPlayer.currentItem)
// Create the SpriteKit video node, containing the video player
let videoSpriteKitNode = SKVideoNode(avPlayer: videoPlayer)
videoSpriteKitNode.position = CGPoint(x: spriteKitScene.size.width / 2.0, y: spriteKitScene.size.height / 2.0)
videoSpriteKitNode.size = spriteKitScene.size
videoSpriteKitNode.yScale = -1.0
videoSpriteKitNode.play()
spriteKitScene.addChild(videoSpriteKitNode)
// Create the SceneKit scene
let scene = SCNScene()
sceneView.scene = scene
sceneView.delegate = self
sceneView.isPlaying = true
// Create a SceneKit plane and add the SpriteKit scene as its material
let background = SCNPlane(width: CGFloat(100), height: CGFloat(100))
background.firstMaterial?.diffuse.contents = spriteKitScene
let backgroundNode = SCNNode(geometry: background)
scene.rootNode.addChildNode(backgroundNode)
...
}
// This callback will restart the video when it has reach its end
func playerItemDidReachEnd(notification: NSNotification) {
if let playerItem: AVPlayerItem = notification.object as? AVPlayerItem {
playerItem.seek(to: kCMTimeZero)
}
}
...
}
2019年解决方案:
let mat = SCNMaterial()
let videoUrl = Bundle.main.url(forResource: "YourVideo", withExtension: "mp4")!
let player = AVPlayer(url: videoUrl)
mat.diffuse.contents = player
player.actionAtItemEnd = .none
NotificationCenter.default.addObserver(self,
selector: #selector(playerItemDidReachEnd(notification:)),
name: .AVPlayerItemDidPlayToEndTime,
object: player.currentItem)
player.play()
选择器中的方法代码:
@objc private func playerItemDidReachEnd(notification: Notification) {
if let playerItem = notification.object as? AVPlayerItem {
playerItem.seek(to: .zero, completionHandler: nil)
}
}
注意:十年来,您不会删除运行选择器的通知。 (你只需要在你使用块的模糊情况下这样做。)
如果你穿越到 2015 年之前:当 object 被释放时,不要忘记删除你的通知观察者! 像NotificationCenter.default
.removeObserver(self,
name: .AVPlayerItemDidPlayToEndTime,
object: player.currentItem)
请注意,apple doco 明确指出您现在可以将视频放在 SCNNode 上。
// make some mesh. whatever size you want.
let mesh = SCNPlane()
mesh.width = 1.77
mesh.height = 1
// put the mesh on your node
yourNode.geometry = mesh
// add the video to the mesh
plr = AVPlayer(url: "https .. .m4v")
yourNode.geometry?.firstMaterial?.diffuse.contents = plr
请注意,您可以在网格上放置任何您想要的东西。 (“几何”是网格。)这很容易。 例如,如果您只想要一种纯色:
... firstMaterial?.diffuse.contents = UIColor.yellow
请注意,该问题询问有关循环播放视频的问题。 这是微不足道的,与使用 SceneKit 无关。 你可以看到一百万个关于循环视频的 QA,就这么简单:
NotificationCenter.default.addObserver(self,
selector: #selector(loopy),
name: .AVPlayerItemDidPlayToEndTime,
object: plr.currentItem)
接着
@objc func loopy() { plr.seek(to: .zero) }
可以使用AVPlayer作为场景背景的内容。 但是,在我将.play(nil)发送到sceneView之前,它不适用于我。
override func viewDidLoad() {
super.viewDidLoad()
// Set the view's delegate
sceneView.delegate = self
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
// Create a new scene
let scene = SCNScene(named: "art.scnassets/ship.scn")!
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// Set the scene to the view
sceneView.scene = scene
let movieFileURL = Bundle.main.url(forResource: "example", withExtension: "mov")!
let player = AVPlayer(url:movieFileURL)
scene.background.contents = player
sceneView.play(nil) //without this line the movie was not playing
player.play()
}
Swift 5.7
我在 macOS Ventura 13.0.1 上的 Xcode 14.1 模拟器 (iOS 16.1) 上测试了这个应用程序。 对于视频纹理,我使用了带有 H.264 编解码器的 QuickTime 1600x900 .mov
文件。 3D 模型的.scn
格式。
import SceneKit
import AVFoundation
import SpriteKit
class GameViewController: UIViewController {
var sceneView: SCNView? = nil
private var avPlayer: AVQueuePlayer? = nil
private var looper: AVPlayerLooper? = nil
override func viewDidLoad() {
super.viewDidLoad()
self.sceneView = self.view as? SCNView
guard let scene = SCNScene(named: "tv.scn") else { return }
sceneView?.scene = scene
sceneView?.allowsCameraControl = true
self.spriteKitScene(self.sceneKitNode())
}
private func spriteKitScene(_ node: SCNNode) { ... } // A
internal func sceneKitNode() -> SCNNode { ... } // B
fileprivate func loadVideoMaterial() -> AVPlayer? { ... } // C
}
SpriteKit 场景能够播放.mov
视频文件:
private func spriteKitScene(_ node: SCNNode) {
let screenGeo: SCNPlane = node.geometry as! SCNPlane
let videoNode = SKVideoNode(avPlayer: self.loadVideoMaterial()!)
let skScene = SKScene(size: CGSize(width: screenGeo.width * 1600,
height: screenGeo.height * 900))
videoNode.position = CGPoint(x: skScene.size.width / 2,
y: skScene.size.height / 2)
videoNode.size = skScene.size
skScene.addChild(videoNode)
let screenMaterial = screenGeo.materials.first
screenMaterial?.diffuse.contents = skScene
videoNode.play()
sceneView?.scene?.rootNode.addChildNode(node)
}
SceneKit 的材质用作 SpriteKit 视频的媒介:
internal func sceneKitNode() -> SCNNode {
if let screen = sceneView?.scene?.rootNode.childNode(withName: "screen",
recursively: false) {
screen.geometry?.firstMaterial?.lightingModel = .constant
screen.geometry?.firstMaterial?.diffuse.contents = UIColor.black
return screen
}
return SCNNode()
}
最后,用于加载视频的方法包含一个AVPlayerLooper
object:
fileprivate func loadVideoMaterial() -> AVPlayer? {
guard let path = Bundle.main.path(forResource: "video", ofType: "mov")
else { return nil }
let videoURL = URL(fileURLWithPath: path)
let asset = AVAsset(url: videoURL)
let item = AVPlayerItem(asset: asset)
self.avPlayer = AVQueuePlayer(playerItem: item)
if let avPlayer {
avPlayer.isMuted = true
self.looper = AVPlayerLooper(player: avPlayer, templateItem: item)
return avPlayer
}
return AVPlayer()
}
您可以采取更短的路线来解决此任务,但问题是,一种方法无法正常工作 - 视频循环播放的延迟等于整个视频的持续时间。
import SceneKit
import AVFoundation
class GameViewController: UIViewController {
var sceneView: SCNView? = nil
private var avPlayer: AVQueuePlayer? = nil
private var looper: AVPlayerLooper? = nil
override func viewDidLoad() {
super.viewDidLoad()
self.sceneView = self.view as? SCNView
sceneView?.isPlaying = true // if view is playing?
guard let scene = SCNScene(named: "tv.scn") else { return }
sceneView?.scene = scene
sceneView?.allowsCameraControl = true
self.loadModelWithVideoMaterial()
}
fileprivate func loadModelWithVideoMaterial() { ... }
}
在这里,我们将AVQueuePlayer
object 分配给材料的内容:
fileprivate func loadModelWithVideoMaterial() {
guard let path = Bundle.main.path(forResource: "video", ofType: "mov")
else { return }
let videoURL = URL(fileURLWithPath: path)
let asset = AVAsset(url: videoURL)
let item = AVPlayerItem(asset: asset)
self.avPlayer = AVQueuePlayer(playerItem: item)
if let avPlayer {
avPlayer.isMuted = true
guard let screen = sceneView?.scene?.rootNode.childNode(
withName: "screen",
recursively: true)
else { return }
screen.geometry?.firstMaterial?.lightingModel = .constant
screen.geometry?.firstMaterial?.diffuse.contents = avPlayer
sceneView?.scene?.rootNode.addChildNode(screen)
self.looper = AVPlayerLooper(player: avPlayer, templateItem: item)
avPlayer.playImmediately(atRate: 20) // speed x20 for testing
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.