1import AppKit
2import AVFoundation
3
4/// Diagnostic (temporary): `sequencer --layertest <key:orig> [<key:orig> …]`.
5/// Builds the same chunk+original stitched compositions the app plays and
6/// shows each in a bare AVPlayerLayer tile — isolating "do N of these render
7/// concurrently" from every other moving part of the app. Seeks all tiles to
8/// SEQ_LAYERTEST_T (default 16.6).
9@MainActor
10func runLayerTest(specs: [String]) {
11 let t = Double(ProcessInfo.processInfo.environment["SEQ_LAYERTEST_T"] ?? "") ?? 16.6
12 let cols = specs.count
13 let tileW = 420.0, tileH = 260.0
14 let win = NSWindow(contentRect: NSRect(x: 100, y: 300,
15 width: tileW * Double(cols), height: tileH),
16 styleMask: [.titled], backing: .buffered, defer: false)
17 win.title = "LayerTest t=\(t)"
18 win.contentView!.wantsLayer = true
19 win.makeKeyAndOrderFront(nil)
20 NSApp.activate(ignoringOtherApps: true)
21
22 for (col, spec) in specs.enumerated() {
23 let bits = spec.split(separator: ":", maxSplits: 1).map(String.init)
24 guard bits.count == 2 else { continue }
25 let key = bits[0], orig = bits[1]
26 let player = AVPlayer()
27 player.automaticallyWaitsToMinimizeStalling = false
28 let layer = AVPlayerLayer(player: player)
29 layer.frame = NSRect(x: Double(col) * tileW, y: 0, width: tileW, height: tileH)
30 layer.videoGravity = .resizeAspect
31 layer.backgroundColor = NSColor.purple.cgColor // un-rendered = purple
32 win.contentView!.layer!.addSublayer(layer)
33 let chunkDir = MediaPipeline.shared.cacheRoot
34 .appendingPathComponent(key).appendingPathComponent("chunks")
35 Task { @MainActor in
36 let comp = AVMutableComposition()
37 let vTrack = comp.addMutableTrack(withMediaType: .video,
38 preferredTrackID: kCMPersistentTrackID_Invalid)!
39 let original = AVURLAsset(url: URL(fileURLWithPath: orig))
40 let origV = try? await original.loadTracks(withMediaType: .video).first
41 let origDur = (try? await original.load(.duration))?.seconds ?? 0
42 for i in 0..<Int(ceil(origDur / 30)) {
43 let startSec = Double(i) * 30
44 let at = CMTime(seconds: startSec, preferredTimescale: 600)
45 let dur = CMTime(seconds: min(30, origDur - startSec),
46 preferredTimescale: 600)
47 let url = chunkDir.appendingPathComponent(String(format: "c%06d.mov", i))
48 if FileManager.default.fileExists(atPath: url.path) {
49 let chunk = AVURLAsset(url: url)
50 if let v = try? await chunk.loadTracks(withMediaType: .video).first {
51 let d = (try? await chunk.load(.duration)) ?? .zero
52 try? vTrack.insertTimeRange(
53 CMTimeRange(start: .zero, duration: min(d, dur)), of: v, at: at)
54 continue
55 }
56 }
57 if let origV {
58 try? vTrack.insertTimeRange(CMTimeRange(start: at, duration: dur),
59 of: origV, at: at)
60 }
61 }
62 let item = AVPlayerItem(asset: comp)
63 item.preferredForwardBufferDuration = 1
64 player.replaceCurrentItem(with: item)
65 while item.status != .readyToPlay {
66 try? await Task.sleep(nanoseconds: 100_000_000)
67 }
68 let t0 = CACurrentMediaTime()
69 nonisolated(unsafe) let layerRef = layer // diagnostic: read on main at completion
70 player.seek(to: CMTime(seconds: t, preferredTimescale: 60000),
71 toleranceBefore: .zero, toleranceAfter: .zero) { _ in
72 NSLog("[layertest] %@ seek landed in %.1fs ready=%d", key,
73 CACurrentMediaTime() - t0, layerRef.isReadyForDisplay ? 1 : 0)
74 }
75 }
76 }
77}