Created
October 25, 2023 23:04
-
-
Save markst/6470df34559439303a481081dc78faf7 to your computer and use it in GitHub Desktop.
`UIViewRepresentable` wrapper for `AVPlayer`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import UIKit | |
import AVKit | |
import SwiftUI | |
import Combine | |
class PlayerUIView: UIView { | |
// MARK: Class Property | |
override static var layerClass: AnyClass { | |
return AVPlayerLayer.self | |
} | |
// MARK: - Helpers | |
var playerLayer: AVPlayerLayer? { | |
layer as? AVPlayerLayer | |
} | |
var player: AVPlayer? { | |
get { | |
return playerLayer?.player | |
} | |
set { | |
playerLayer?.player = newValue | |
} | |
} | |
} | |
struct PlayerView: UIViewRepresentable { | |
@Binding var player: AVPlayer | |
@Binding var visible: Bool | |
let onVideoCompleted: VoidClosure | |
let onVideoError: VoidClosure | |
var videoBackground: UIColor = .clear | |
var videoGravity: AVLayerVideoGravity = .resizeAspectFill | |
// MARK: - UIViewRepresentable | |
func makeUIView(context: Context) -> PlayerUIView { | |
PlayerUIView() | |
} | |
func updateUIView(_ uiView: PlayerUIView, context: UIViewRepresentableContext<PlayerView>) { | |
uiView.playerLayer?.videoGravity = videoGravity | |
uiView.playerLayer?.player = visible ? player : nil | |
uiView.backgroundColor = videoBackground | |
} | |
// MARK: - | |
func makeCoordinator() -> PlayerView.Coordinator { | |
Coordinator(self, onVideoCompleted: onVideoCompleted, onVideoError: onVideoError) | |
} | |
class Coordinator: NSObject { | |
let parent: PlayerView | |
let onVideoCompleted: VoidClosure | |
private var cancellables = Set<AnyCancellable>() | |
init( | |
_ parent: PlayerView, | |
onVideoCompleted: @escaping VoidClosure, | |
onVideoError: @escaping VoidClosure | |
) { | |
self.parent = parent | |
self.onVideoCompleted = onVideoCompleted | |
super.init() | |
let currentItem = parent.player | |
.publisher(for: \.currentItem) | |
.compactMap({ $0 }) | |
currentItem | |
.flatMapLatest({ NotificationCenter.default.publisher(for: .AVPlayerItemDidPlayToEndTime, object: $0) }) | |
.share() | |
.sink(receiveValue: { [onVideoCompleted] _ in | |
onVideoCompleted() | |
}) | |
.store(in: &cancellables) | |
currentItem | |
.flatMapLatest({ $0.publisher(for: \.status) }) | |
.share() | |
.filter({ $0 == .failed }) | |
.sink(receiveValue: { [onVideoError] _ in | |
onVideoError() | |
}) | |
.store(in: &cancellables) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment