Skip to content

Commit a485cee

Browse files
committed
Add tests for GreedyPlayerView
1 parent 17d1633 commit a485cee

17 files changed

+461
-88
lines changed

.swiftlint.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ opt_in_rules:
1818
- comment_spacing
1919
- force_unwrapping
2020
- sorted_imports
21+
- void_function_in_ternary
2122

2223
line_length: 120
2324
warning_threshold: 3

Package.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ let package = Package(
2222
),
2323
.testTarget(
2424
name: "GreedyKitTests",
25-
dependencies: ["GreedyKit"]
25+
dependencies: ["GreedyKit"],
26+
resources: [
27+
.process("Resources")
28+
]
2629
)
2730
]
2831
)

Sources/GreedyKit/Core/Rendering/VideoRenderActor.swift renamed to Sources/GreedyKit/Core/Rendering/VideoRenderer.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// VideoRenderActor.swift
2+
// VideoRenderer.swift
33
// GreedyKit
44
//
55
// Created by Igor Belov on 30.06.2025.
@@ -8,7 +8,7 @@
88
import AVFoundation
99
import CoreImage
1010

11-
final actor VideoRenderActor {
11+
final actor VideoRenderer: VideoRendererProtocol {
1212

1313
private lazy var sampleBufferFactory = SampleBufferFactory()
1414
private lazy var videoOutput = AVPlayerItemVideoOutput(

Sources/GreedyKit/Core/Utils/BackedRenderView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import AVFoundation
99
import UIKit
1010

11-
final class BackedRenderView: UIView {
11+
final class BackedRenderView: UIView, RenderViewProtocol {
1212

1313
// MARK: - Internal API
1414

Sources/GreedyKit/Extensions/ConcurrencyShims.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77

88
import AVFoundation
99

10-
/// Safe because after attachment to an `AVPlayerItem`, we only access the output's
11-
/// read-only methods from a single actor.
10+
/// Safe because after attachment to an `AVPlayerItem`,
11+
/// we only access the output's read-only methods from a single actor.
1212
/// No concurrent mutation occurs in our usage.
1313
extension AVPlayerItemVideoOutput: @unchecked @retroactive Sendable {}
1414

Sources/GreedyKit/Extensions/BackedRenderView+Extensions.swift renamed to Sources/GreedyKit/Extensions/RenderViewProtocol+Extensions.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
//
2-
// UIView+Extensions.swift
2+
// RenderViewProtocol+Extensions.swift
33
// GreedyKit
44
//
55
// Created by Igor Belov on 05.09.2022.
66
//
77

88
import UIKit
99

10-
extension BackedRenderView {
10+
extension RenderViewProtocol {
1111
func configure(in superview: UIView) {
1212
translatesAutoresizingMaskIntoConstraints = false
1313

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//
2+
// DisplayLinkProtocol.swift
3+
// GreedyKit
4+
//
5+
// Created by Igor Belov on 04.07.2025.
6+
//
7+
8+
import UIKit
9+
10+
protocol DisplayLinkProtocol: AnyObject {
11+
var isPaused: Bool { get set }
12+
13+
func add(to runLoop: RunLoop, forMode: RunLoop.Mode)
14+
func invalidate()
15+
}
16+
17+
extension CADisplayLink: DisplayLinkProtocol {}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//
2+
// RenderViewProtocol.swift
3+
// GreedyKit
4+
//
5+
// Created by Igor Belov on 04.07.2025.
6+
//
7+
8+
import AVFoundation
9+
import UIKit
10+
11+
protocol RenderViewProtocol: UIView {
12+
var preventsCapture: Bool { get set }
13+
var contentGravity: AVLayerVideoGravity { get set }
14+
15+
func enqueueBuffer(_ buffer: CMSampleBuffer) async
16+
func clearLayer() async
17+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//
2+
// VideoRendererProtocol.swift
3+
// GreedyKit
4+
//
5+
// Created by Igor Belov on 04.07.2025.
6+
//
7+
8+
import AVFoundation
9+
10+
protocol VideoRendererProtocol: Sendable {
11+
func attach(to item: AVPlayerItem) async
12+
func frame(at time: CMTime) async -> CMSampleBuffer?
13+
}

Sources/GreedyKit/UIKit/GreedyPlayerView.swift

Lines changed: 59 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -48,51 +48,75 @@ public final class GreedyPlayerView: UIView {
4848

4949
// MARK: - Properties
5050

51-
private lazy var renderer = VideoRenderActor()
51+
typealias DisplayLinkFactory = (
52+
_ target: Any,
53+
_ selector: Selector
54+
) -> DisplayLinkProtocol
5255

53-
private let renderView = BackedRenderView()
54-
private var playerItemObserver: AnyCancellable?
56+
private var displayLink: DisplayLinkProtocol?
57+
private let renderer: VideoRendererProtocol
58+
private let renderView: RenderViewProtocol
5559

56-
private lazy var displayLink = CADisplayLink(
57-
target: self,
58-
selector: #selector(displayLinkDidRefresh(link:))
59-
)
60+
private let displayLinkFactory: DisplayLinkFactory
61+
private var playerItemObserver: AnyCancellable?
6062

6163
// MARK: - Lifecycle
6264

63-
public override init(frame: CGRect) {
65+
init(
66+
frame: CGRect = .zero,
67+
renderer: VideoRendererProtocol,
68+
renderView: RenderViewProtocol,
69+
displayLinkFactory: @escaping DisplayLinkFactory
70+
) {
71+
self.renderer = renderer
72+
self.renderView = renderView
73+
self.displayLinkFactory = displayLinkFactory
6474
super.init(frame: frame)
75+
6576
renderView.configure(in: self)
6677
}
6778

79+
public override convenience init(frame: CGRect) {
80+
self.init(
81+
frame: frame,
82+
renderer: VideoRenderer(),
83+
renderView: BackedRenderView(),
84+
displayLinkFactory: { target, selector in
85+
CADisplayLink(target: target, selector: selector)
86+
}
87+
)
88+
}
89+
6890
required init?(coder: NSCoder) {
6991
fatalError("init(coder:) has not been implemented")
7092
}
7193

72-
public override func willMove(toSuperview newSuperview: UIView?) {
73-
if newSuperview == nil {
74-
dismantle()
75-
}
76-
}
94+
public override func didMoveToWindow() {
95+
super.didMoveToWindow()
7796

78-
public override func didMoveToSuperview() {
79-
super.didMoveToSuperview()
80-
initializeDisplayLink()
97+
if window == nil {
98+
pauseRendering()
99+
} else {
100+
resumeRendering()
101+
}
81102
}
82103

83104
// MARK: - Private Methods
84105

85-
private func dismantle() {
86-
displayLink.invalidate()
87-
playerItemObserver?.cancel()
106+
private func resumeRendering() {
107+
if displayLink == nil {
108+
displayLink = displayLinkFactory(self, #selector(displayLinkDidRefresh))
109+
displayLink?.add(to: .current, forMode: .common)
110+
}
111+
displayLink?.isPaused = (player?.currentItem == nil)
88112
}
89113

90-
private func initializeDisplayLink() {
91-
displayLink.add(to: .current, forMode: .common)
92-
displayLink.isPaused = true
114+
private func pauseRendering() {
115+
displayLink?.invalidate()
116+
displayLink = nil
93117
}
94118

95-
@objc private func displayLinkDidRefresh(link: CADisplayLink) {
119+
@objc private func displayLinkDidRefresh() {
96120
guard let player else { return }
97121
let itemTime = player.currentTime()
98122

@@ -104,17 +128,24 @@ public final class GreedyPlayerView: UIView {
104128
}
105129
}
106130

131+
private func attachAndResumeIfNeeded(_ item: AVPlayerItem) async {
132+
await renderer.attach(to: item)
133+
if window != nil {
134+
displayLink?.isPaused = false
135+
}
136+
}
137+
107138
private func addPlayerItemObserver() {
108-
guard let player else { return }
139+
guard let player else {
140+
displayLink?.isPaused = true
141+
return
142+
}
109143

110144
playerItemObserver = player.publisher(for: \.currentItem)
111145
.compactMap { $0 }
112146
.sink { [weak self] item in
113147
guard let self else { return }
114-
Task {
115-
await self.renderer.attach(to: item)
116-
self.displayLink.isPaused = false
117-
}
148+
Task { await attachAndResumeIfNeeded(item) }
118149
}
119150
}
120151
}

0 commit comments

Comments
 (0)