@@ -10,72 +10,93 @@ import UIKit
1010import AVKit
1111
1212/// A looping video-view based off of AVPlayerLayer. Automatically scales video to aspect-fill, but can be manually set otherwise. Responds to UIView animations and scales accordingly.
13+
1314@available ( iOS 12 . 0 , * )
1415public class SDLoopingVideoView : UIView {
1516
1617 enum VideoPropertiesNotSetError : Error {
1718 case runtimeError( String )
1819 }
1920
20- @IBInspectable private var video_Light : String ?
21- @IBInspectable private var video_Night : String ?
21+ // MARK: Private Variables
22+
23+ @IBInspectable private var videoName : String ?
2224 @IBInspectable private var videoType : String ?
25+ @IBInspectable private var videoNameDarkMode : String ?
26+ @IBInspectable private var videoTypeDarkMode : String ?
27+
28+ private var videoScaling : AVLayerVideoGravity ?
29+ private var videoScalingDarkMode : AVLayerVideoGravity ?
2330 private var player : AVPlayer ?
2431 private var playerLayer : AVPlayerLayer { get { return ( self . layer as! AVPlayerLayer ) } }
25- public var scaling : AVLayerVideoGravity ?
32+
2633 override public class var layerClass : AnyClass { return AVPlayerLayer . self }
2734
35+ // MARK: Construction
2836
2937 /**
3038 Initializes a new SDLoopingVideoView.
3139
3240 - Parameters:
3341 - frame: The frame of the view
34- - videoName : The name of the video file to play and loop
35- - videoType: The file extension of the video file
42+ - video : The default video to play on loop
43+ - darkModeVideo: An optional video to play when dark mode is active
3644
3745 - Returns: An SDLoopingVideoView
3846 */
39- public init ( frame: CGRect , videoName_Light: String , videoName_Night: String ? , videoType: String , scaling: AVLayerVideoGravity = . resizeAspectFill) {
40- self . video_Light = videoName_Light
41- self . video_Night = videoName_Night
42- self . videoType = videoType
43- self . scaling = scaling
47+
48+ public init ( frame: CGRect , video: SDVideo , darkModeVideo: SDVideo ? = nil ) {
49+ self . videoName = video. fileName
50+ self . videoNameDarkMode = darkModeVideo? . fileName
51+ self . videoType = video. fileNameWithExtension
52+ self . videoTypeDarkMode = darkModeVideo? . fileExtension
53+ self . videoScaling = video. gravity
54+ self . videoScalingDarkMode = darkModeVideo? . gravity
4455 super. init ( frame: frame)
4556 attemptVideoSetup ( )
4657 }
4758
59+ required init ? ( coder aDecoder: NSCoder ) {
60+ super. init ( coder: aDecoder)
61+ }
62+
63+ // MARK: Setup
64+
4865 private func setupVideoView( ) throws {
49- guard video_Light != nil && video_Night != nil && videoType != nil else {
66+ guard videoIsSet else {
5067 throw VideoPropertiesNotSetError . runtimeError ( " Video name or video type not set " )
5168 }
69+ guard darkModeVideoIsValid else {
70+ throw VideoPropertiesNotSetError . runtimeError ( " Dark mode video name or video type not set " )
71+ }
72+ guard !darkModeVideoWithoutDefaultVideo else {
73+ throw VideoPropertiesNotSetError . runtimeError ( " A dark mode video can not be set without a default video also set " )
74+ }
5275 if player == nil {
5376 self . backgroundColor = UIColor . clear
77+ let video = videoForUserInterfaceStyle
5478 DispatchQueue . global ( qos: . userInitiated) . async { [ weak self] in
55-
56- DispatchQueue . main. async {
57- var video : String {
58- switch self ? . traitCollection. userInterfaceStyle {
59- case . light, . unspecified, . none:
60- return self !. video_Light!
61- case . dark:
62- return self !. video_Night!
63- }
64- }
65- guard let path = Bundle . main. path ( forResource: video, ofType: self ? . videoType!) else { debugPrint ( " video file not found " )
66- return }
67- self ? . player = AVPlayer ( url: URL ( fileURLWithPath: path) )
68- self ? . player? . isMuted = true
69- self ? . playerLayer. player = self ? . player
70- self ? . playerLayer. videoGravity = self ? . scaling ?? AVLayerVideoGravity . resizeAspectFill
71- self ? . player!. play ( )
72- NotificationCenter . default. addObserver ( forName: . AVPlayerItemDidPlayToEndTime, object: self ? . player!. currentItem, queue: . main) { [ weak self] _ in
73- self ? . player? . seek ( to: CMTime . zero)
74- self ? . player? . play ( )
79+ guard let self = self , let path = Bundle . main. path ( forResource: video. fileName, ofType: video. fileExtension) else {
80+ debugPrint ( " video file not found " )
81+ return
82+ }
83+ let playerItem = AVPlayerItem ( url: URL ( fileURLWithPath: path) )
84+ DispatchQueue . main. async { [ weak self] in
85+ guard let self = self else { return }
86+ self . player = AVPlayer ( playerItem: playerItem)
87+ self . player? . isMuted = true
88+ self . playerLayer. player = self . player
89+ self . playerLayer. videoGravity = video. gravity
90+ self . player!. play ( )
91+ NotificationCenter . default. addObserver ( forName: . AVPlayerItemDidPlayToEndTime, object: self . player!. currentItem, queue: . main) { [ weak self] _ in
92+ guard let player = self ? . player else { return }
93+ player. seek ( to: CMTime . zero)
94+ player. play ( )
7595 }
7696 NotificationCenter . default. addObserver ( forName: UIApplication . willEnterForegroundNotification, object: nil , queue: . main) { [ weak self] _ in
77- self ? . player? . seek ( to: CMTime . zero)
78- self ? . player? . play ( )
97+ guard let player = self ? . player else { return }
98+ player. seek ( to: CMTime . zero)
99+ player. play ( )
79100 }
80101 }
81102 }
@@ -92,8 +113,13 @@ public class SDLoopingVideoView: UIView {
92113 }
93114 }
94115
95- required init ? ( coder aDecoder: NSCoder ) {
96- super. init ( coder: aDecoder)
116+ // MARK: UIView
117+
118+ override public func traitCollectionDidChange( _ previousTraitCollection: UITraitCollection ? ) {
119+ if previousTraitCollection? . userInterfaceStyle != traitCollection. userInterfaceStyle {
120+ player = nil
121+ attemptVideoSetup ( )
122+ }
97123 }
98124
99125 override public func layoutSubviews( ) {
@@ -102,3 +128,32 @@ public class SDLoopingVideoView: UIView {
102128 }
103129
104130}
131+
132+ extension SDLoopingVideoView {
133+
134+ // MARK: Computed Variables
135+
136+ private var videoIsSet : Bool {
137+ videoName != nil && videoType != nil
138+ }
139+
140+ private var darkModeVideoIsSet : Bool {
141+ videoNameDarkMode != nil && videoTypeDarkMode != nil
142+ }
143+
144+ private var darkModeVideoIsValid : Bool {
145+ darkModeVideoIsSet || !darkModeVideoIsSet
146+ }
147+
148+ private var darkModeVideoWithoutDefaultVideo : Bool {
149+ return darkModeVideoIsSet && !videoIsSet
150+ }
151+
152+ private var videoForUserInterfaceStyle : SDVideo {
153+ if let dmv = videoNameDarkMode, let dmvt = videoTypeDarkMode, traitCollection. userInterfaceStyle == . dark {
154+ return . video( fileName: dmv, fileExtension: SDVideoExtension ( from: dmvt) , scaling: videoScalingDarkMode ?? . resizeAspectFill)
155+ }
156+ return . video( fileName: videoName ?? " " , fileExtension: SDVideoExtension ( from: videoType ?? " " ) , scaling: videoScaling ?? . resizeAspectFill)
157+ }
158+
159+ }
0 commit comments