When the view loads on device I just have a black screen. I have very similar code working with a Storyboard instance and using UIViewController, but I need to get away from that as I am working with visionOS and would like to abstract this away into using RealityKit and VideoPlayerLayer, but for prototyping and iterating other tasks I would like this AVSampleBufferDisplayLayer to work... which ultimately I will have to pass an AVSampleBufferVideoRenderer to VideoPlayerLayer so getting this implementation works helps me dive into the RealityKit rendering.
Here is the code, I've stuffed everything into one file for ease of debugging and questions.
Thank you!
struct MirrorView: View {
var body: some View {
VStack {
LayerView()
}
}
}
struct LayerView: UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
print("LayerUIView is being created")
return LayerUIView()
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<LayerView>) {
print("LayerUIView is being updated")
}
}
class LayerUIView: UIView {
private let networking = Networking()
private let displayLayer = AVSampleBufferDisplayLayer()
private var subscriptions = Set<AnyCancellable>()
private var sampleBufferTask: Task<Void, Never>?
override init(frame: CGRect) {
super.init(frame: frame)
print("LayerUIView initialized")
setupVideoLayer()
setupNetworking()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
layer.frame = bounds
}
private func setupVideoLayer() {
displayLayer.frame = bounds
displayLayer.videoGravity = .resizeAspect
layer.addSublayer(displayLayer)
NotificationCenter.default.addObserver(
self,
selector: #selector(handleFailedToDecodeNotification(_:)),
name: .AVSampleBufferDisplayLayerFailedToDecode,
object: displayLayer
)
}
@objc private func handleFailedToDecodeNotification(_ notification: Notification) {
if let error = notification.userInfo?[AVSampleBufferDisplayLayerFailedToDecodeNotificationErrorKey] {
print("Failed to decode sample buffer. Error: \(error)")
} else {
print("Failed to decode sample buffer. No error information available.")
}
}
private func setupNetworking() {
networking.startAdvertising()
print("Networking is connected: \(networking.isConnected)")
startSampleBufferTask()
}
// MARK: - Task Management
private func startSampleBufferTask() {
sampleBufferTask = Task {
for await sampleBuffer in networking.sampleBuffers {
let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer)
print("Format Description: \(String(describing: formatDescription))")
let presentationTimeStamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
print("Presentation Timestamp: \(presentationTimeStamp)")
let duration = CMSampleBufferGetDuration(sampleBuffer)
print("Duration: \(duration)")
DispatchQueue.main.async {
self.displayLayer.sampleBufferRenderer.enqueue(sampleBuffer)
}
}
}
}
private func stopSampleBufferTask() {
sampleBufferTask?.cancel()
sampleBufferTask = nil
}
}
#Preview {
MirrorView()
}
I created a UIKit version of this and successfully loaded as an iOS device in the AVP's.
Here is the ViewController running that code:
class ViewController: UIViewController { //, VideoDecoderAnnexBAdaptorDelegate {
// MARK: - Properties
private let networking = Networking()
private let displayLayer = AVSampleBufferDisplayLayer()
private var subscriptions = Set<AnyCancellable>()
private var sampleBufferTask: Task<Void, Never>?
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupVideoLayer()
setupNetworking()
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .allButUpsideDown
}
override var shouldAutorotate: Bool {
return true
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { _ in
// Adjust layout for new orientation
self.displayLayer.frame = self.view.bounds
}, completion: nil)
}
// MARK: - Setup Methods
private func setupVideoLayer() {
displayLayer.frame = view.bounds
displayLayer.videoGravity = .resizeAspect
displayLayer.backgroundColor = UIColor.black.cgColor
view.layer.addSublayer(displayLayer)
NotificationCenter.default.addObserver(
self,
selector: #selector(handleFailedToDecodeNotification(_:)),
name: .AVSampleBufferDisplayLayerFailedToDecode,
object: displayLayer
)
}
@objc private func handleFailedToDecodeNotification(_ notification: Notification) {
if let error = notification.userInfo?[AVSampleBufferDisplayLayerFailedToDecodeNotificationErrorKey] {
print("Failed to decode sample buffer. Error: \(error)")
} else {
print("Failed to decode sample buffer. No error information available.")
}
}
private func setupNetworking() {
networking.startAdvertising()
print("Networking is connected: \(networking.isConnected)")
startSampleBufferTask()
}
// MARK: - Task Management
private func startSampleBufferTask() {
sampleBufferTask = Task {
for await sampleBuffer in networking.sampleBuffers {
let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer)
print("Format Description: \(String(describing: formatDescription))")
let presentationTimeStamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
print("Presentation Timestamp: \(presentationTimeStamp)")
let duration = CMSampleBufferGetDuration(sampleBuffer)
print("Duration: \(duration)")
DispatchQueue.main.async {
self.displayLayer.sampleBufferRenderer.enqueue(sampleBuffer)
}
}
}
}
private func stopSampleBufferTask() {
sampleBufferTask?.cancel()
sampleBufferTask = nil
}
}
In your init LayerView
code:
func makeUIView(context: Context) -> UIView {
print("LayerUIView is being created")
return LayerUIView()
}
Initial frame
for LayerUIView
is .zero
. Therefore your displayLayer
's frame
is initially .zero
so it is not show properly on screen, moreover after LayerUIView
is init, displayeLayer
's frame
is not updated anymore.
So in order to fix your problem, you need to update frame
for displayLayer
when layoutSubviews
too. In LayerUIView
, instead of update frame
for layer
:
override func layoutSubviews() {
super.layoutSubviews()
layer.frame = bounds
}
Use this code instead:
override func layoutSubviews() {
super.layoutSubviews()
displayLayer.frame = bounds
}