Skip to content

Commit b356831

Browse files
committed
added support for stretched videos
1 parent 9f78250 commit b356831

File tree

1 file changed

+108
-29
lines changed

1 file changed

+108
-29
lines changed

Sources/BuilderIO/Components/BuilderVideo.swift

Lines changed: 108 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ struct BuilderVideo: BuilderViewProtocol {
2222

2323
// AVPlayer
2424
@State private var player: AVPlayer?
25-
@State private var playerLayer: AVPlayerLayer?
26-
// New state to manage play button visibility and video playing state
25+
2726
@State private var showOverlay: Bool
2827
@State private var isPlaying: Bool = false
2928

@@ -76,31 +75,45 @@ struct BuilderVideo: BuilderViewProtocol {
7675
var body: some View {
7776
Group {
7877
if let videoURL = videoURL {
79-
VideoPlayer(player: player)
80-
.onAppear {
81-
player = AVPlayer(url: videoURL)
82-
playerLayer = AVPlayerLayer(player: player)
83-
playerLayer?.videoGravity =
84-
self.contentMode == .fill ? .resizeAspectFill : .resizeAspect
85-
player?.isMuted = muted
86-
87-
if loop {
88-
NotificationCenter.default.addObserver(
89-
forName: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: .main
90-
) { [self] _ in
91-
self.player?.seek(to: CMTime.zero)
92-
self.player?.play()
93-
}
94-
}
95-
96-
if autoPlay {
97-
self.showOverlay = false
98-
player?.play()
99-
}
78+
Rectangle().fill(Color.clear)
79+
.aspectRatio(self.aspectRatio ?? 1, contentMode: self.contentMode)
80+
.if(contentMode == .fill) { view in
81+
view.background(
82+
VideoPlayerView(
83+
videoURL: videoURL,
84+
autoPlay: true,
85+
muted: self.muted,
86+
loop: self.loop
87+
)
88+
)
10089
}
101-
.onDisappear {
102-
player?.pause()
103-
player = nil
90+
.if(contentMode == .fit) { view in
91+
view.background(
92+
VideoPlayer(player: player)
93+
.onAppear {
94+
player = AVPlayer(url: videoURL)
95+
player?.isMuted = muted
96+
97+
if loop {
98+
NotificationCenter.default.addObserver(
99+
forName: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem,
100+
queue: .main
101+
) { [self] _ in
102+
self.player?.seek(to: CMTime.zero)
103+
self.player?.play()
104+
}
105+
}
106+
107+
if autoPlay {
108+
self.showOverlay = false
109+
player?.play()
110+
}
111+
}.onDisappear {
112+
player?.pause()
113+
player = nil
114+
}
115+
116+
)
104117
}
105118
.aspectRatio(aspectRatio, contentMode: self.contentMode)
106119
.overlay(
@@ -141,9 +154,6 @@ struct BuilderVideo: BuilderViewProtocol {
141154
}
142155
}
143156
)
144-
.onChange(of: muted) { newMuted in
145-
player?.isMuted = newMuted
146-
}
147157

148158
} else {
149159
// If no video URL, display poster image or an empty view
@@ -152,3 +162,72 @@ struct BuilderVideo: BuilderViewProtocol {
152162
}
153163
}
154164
}
165+
166+
struct VideoPlayerView: UIViewRepresentable {
167+
var videoURL: URL
168+
var autoPlay: Bool
169+
var muted: Bool
170+
var loop: Bool
171+
172+
func makeUIView(context: Context) -> PlayerContainerView {
173+
let playerContainerView = PlayerContainerView(
174+
videoURL: videoURL,
175+
autoPlay: autoPlay,
176+
muted: muted,
177+
loop: loop
178+
)
179+
return playerContainerView
180+
}
181+
182+
func updateUIView(_ uiView: PlayerContainerView, context: Context) {
183+
184+
uiView.player?.isMuted = muted
185+
186+
}
187+
188+
// 2. Custom UIView subclass to host the AVPlayerLayer
189+
class PlayerContainerView: UIView {
190+
var player: AVPlayer? // Make it optional to handle init scenarios
191+
private var playerLayer: AVPlayerLayer!
192+
private var playerLooper: AVPlayerLooper?
193+
194+
init(videoURL: URL, autoPlay: Bool, muted: Bool, loop: Bool) {
195+
super.init(frame: .zero)
196+
self.backgroundColor = .black
197+
198+
// For seamless looping, use AVPlayerLooper with a queue player
199+
// This also handles the initial autoPlay.
200+
let playerItem = AVPlayerItem(url: videoURL)
201+
let queuePlayer = AVQueuePlayer(playerItem: playerItem)
202+
203+
player = queuePlayer // Assign to the optional player property
204+
playerLayer = AVPlayerLayer(player: queuePlayer)
205+
playerLayer.videoGravity = .resizeAspectFill
206+
207+
self.layer.addSublayer(playerLayer)
208+
209+
// Apply mute setting
210+
player?.isMuted = muted
211+
212+
// Handle looping
213+
if loop {
214+
playerLooper = AVPlayerLooper(player: queuePlayer, templateItem: playerItem)
215+
}
216+
217+
// Handle autoPlay
218+
if autoPlay {
219+
queuePlayer.play()
220+
}
221+
222+
}
223+
224+
required init?(coder: NSCoder) {
225+
fatalError("init(coder:) has not been implemented")
226+
}
227+
228+
override func layoutSubviews() {
229+
super.layoutSubviews()
230+
playerLayer.frame = bounds
231+
}
232+
}
233+
}

0 commit comments

Comments
 (0)