Skip to content

Commit 8d97445

Browse files
authored
Merge pull request #4 from SolsmaHawk/jsolsma/v2.0
Update to v.2.0 + Fix dark mode functionality + Refactor and re-architect instantiation
2 parents 7ba39bd + a3e1fa7 commit 8d97445

10 files changed

Lines changed: 356 additions & 45 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//
2+
// SDLoopingVideoView.SDVideo.swift
3+
// SDLoopingVideoViewExample
4+
//
5+
// Created by John Solsma on 3/21/20.
6+
// Copyright © 2020 Solsma Dev Inc. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import AVKit
11+
12+
extension SDLoopingVideoView {
13+
14+
public enum SDVideo {
15+
case video(fileName: String, fileExtension: SDVideoExtension, scaling: AVLayerVideoGravity = .resizeAspectFill)
16+
17+
var fileNameWithExtension: String {
18+
fileName + "." + fileExtension
19+
}
20+
21+
var fileName: String {
22+
switch self {
23+
case .video(let fileName, _, _):
24+
return fileName
25+
}
26+
}
27+
28+
var fileExtension: String {
29+
switch self {
30+
case .video(_, let fileExtension, _):
31+
return fileExtension.stringRepresentation
32+
}
33+
}
34+
35+
var gravity: AVLayerVideoGravity {
36+
switch self {
37+
case .video(_, _, let scaling):
38+
return scaling
39+
}
40+
}
41+
42+
}
43+
44+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//
2+
// SDLoopingVideoView.VideoType.swift
3+
// SDLoopingVideoViewExample
4+
//
5+
// Created by John Solsma on 3/21/20.
6+
// Copyright © 2020 Solsma Dev Inc. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
extension SDLoopingVideoView {
12+
13+
public enum SDVideoExtension {
14+
case mpg
15+
case wmv
16+
case avi
17+
case mkv
18+
case rmvb
19+
case rm
20+
case mp4
21+
case mov
22+
case custom(fileExtension: String)
23+
}
24+
25+
}
26+
27+
extension SDLoopingVideoView.SDVideoExtension {
28+
29+
init(from string: String) {
30+
switch string {
31+
case "mpg":
32+
self = .mkv
33+
case "wmv":
34+
self = .wmv
35+
case "avi":
36+
self = .avi
37+
case "mkv":
38+
self = .mkv
39+
case "rmvb":
40+
self = .rmvb
41+
case "rm":
42+
self = .rm
43+
case "mp4":
44+
self = .mp4
45+
case "mov":
46+
self = .mov
47+
default:
48+
self = .custom(fileExtension: string)
49+
}
50+
}
51+
52+
var stringRepresentation: String {
53+
switch self {
54+
case .custom(let fileExtension):
55+
return fileExtension
56+
case .mpg:
57+
return "mpg"
58+
case .wmv:
59+
return "wmv"
60+
case .avi:
61+
return "avi"
62+
case .mkv:
63+
return "mkv"
64+
case .rmvb:
65+
return "rmvb"
66+
case .rm:
67+
return "rm"
68+
case .mp4:
69+
return "mp4"
70+
case .mov:
71+
return "mov"
72+
}
73+
}
74+
75+
}

SDLoopingVideoView/SDLoopingVideoView.swift

Lines changed: 90 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,72 +10,93 @@ import UIKit
1010
import 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, *)
1415
public 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+
}

SDLoopingVideoViewExample.xcodeproj/project.pbxproj

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
31420D322426B3B600A7B409 /* SDLoopingVideoView.VideoExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31420D312426B3B600A7B409 /* SDLoopingVideoView.VideoExtension.swift */; };
11+
31420D342426BA4200A7B409 /* SDLoopingVideoView.SDVideo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31420D332426BA4200A7B409 /* SDLoopingVideoView.SDVideo.swift */; };
12+
31420D3724270AAD00A7B409 /* nyc_skyline_day.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 31420D3524270AAC00A7B409 /* nyc_skyline_day.mp4 */; };
13+
31420D3824270AAD00A7B409 /* nyc_skyline_night.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 31420D3624270AAC00A7B409 /* nyc_skyline_night.mp4 */; };
14+
31420D3A24270D0E00A7B409 /* DarkModeDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31420D3924270D0E00A7B409 /* DarkModeDemoViewController.swift */; };
1015
31E948A22220CE4400C6A14C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E948A12220CE4400C6A14C /* AppDelegate.swift */; };
1116
31E948A42220CE4400C6A14C /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E948A32220CE4400C6A14C /* ViewController.swift */; };
1217
31E948A72220CE4400C6A14C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 31E948A52220CE4400C6A14C /* Main.storyboard */; };
@@ -20,6 +25,11 @@
2025
/* End PBXBuildFile section */
2126

2227
/* Begin PBXFileReference section */
28+
31420D312426B3B600A7B409 /* SDLoopingVideoView.VideoExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDLoopingVideoView.VideoExtension.swift; sourceTree = "<group>"; };
29+
31420D332426BA4200A7B409 /* SDLoopingVideoView.SDVideo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDLoopingVideoView.SDVideo.swift; sourceTree = "<group>"; };
30+
31420D3524270AAC00A7B409 /* nyc_skyline_day.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = nyc_skyline_day.mp4; sourceTree = "<group>"; };
31+
31420D3624270AAC00A7B409 /* nyc_skyline_night.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = nyc_skyline_night.mp4; sourceTree = "<group>"; };
32+
31420D3924270D0E00A7B409 /* DarkModeDemoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DarkModeDemoViewController.swift; sourceTree = "<group>"; };
2333
31E9489E2220CE4400C6A14C /* SDLoopingVideoViewExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SDLoopingVideoViewExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
2434
31E948A12220CE4400C6A14C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
2535
31E948A32220CE4400C6A14C /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
@@ -74,6 +84,7 @@
7484
31E948B52220D1F500C6A14C /* Videos */,
7585
31E948A12220CE4400C6A14C /* AppDelegate.swift */,
7686
31E948A32220CE4400C6A14C /* ViewController.swift */,
87+
31420D3924270D0E00A7B409 /* DarkModeDemoViewController.swift */,
7788
31E948A52220CE4400C6A14C /* Main.storyboard */,
7889
31E948A82220CE4700C6A14C /* Assets.xcassets */,
7990
31E948AA2220CE4700C6A14C /* LaunchScreen.storyboard */,
@@ -87,6 +98,8 @@
8798
children = (
8899
31E948B62220D1FC00C6A14C /* solsmaDev_logo.mov */,
89100
31E948B72220D1FC00C6A14C /* velocityBreaker_video.mov */,
101+
31420D3524270AAC00A7B409 /* nyc_skyline_day.mp4 */,
102+
31420D3624270AAC00A7B409 /* nyc_skyline_night.mp4 */,
90103
);
91104
path = Videos;
92105
sourceTree = "<group>";
@@ -95,6 +108,8 @@
95108
isa = PBXGroup;
96109
children = (
97110
31E948B32220CED000C6A14C /* SDLoopingVideoView.swift */,
111+
31420D312426B3B600A7B409 /* SDLoopingVideoView.VideoExtension.swift */,
112+
31420D332426BA4200A7B409 /* SDLoopingVideoView.SDVideo.swift */,
98113
);
99114
path = SDLoopingVideoView;
100115
sourceTree = "<group>";
@@ -141,6 +156,7 @@
141156
TargetAttributes = {
142157
31E9489D2220CE4400C6A14C = {
143158
CreatedOnToolsVersion = 10.1;
159+
LastSwiftMigration = 1130;
144160
};
145161
};
146162
};
@@ -167,13 +183,15 @@
167183
isa = PBXResourcesBuildPhase;
168184
buildActionMask = 2147483647;
169185
files = (
186+
31420D3824270AAD00A7B409 /* nyc_skyline_night.mp4 in Resources */,
170187
31E948B82220D1FC00C6A14C /* solsmaDev_logo.mov in Resources */,
171188
31E948C82222F92900C6A14C /* SDLoopingVideoView.podspec in Resources */,
172189
31E948AC2220CE4700C6A14C /* LaunchScreen.storyboard in Resources */,
173190
31E948CA2222FA1000C6A14C /* LICENSE in Resources */,
174191
31E948A92220CE4700C6A14C /* Assets.xcassets in Resources */,
175192
31E948B92220D1FC00C6A14C /* velocityBreaker_video.mov in Resources */,
176193
31E948A72220CE4400C6A14C /* Main.storyboard in Resources */,
194+
31420D3724270AAD00A7B409 /* nyc_skyline_day.mp4 in Resources */,
177195
);
178196
runOnlyForDeploymentPostprocessing = 0;
179197
};
@@ -185,6 +203,9 @@
185203
buildActionMask = 2147483647;
186204
files = (
187205
31E948A42220CE4400C6A14C /* ViewController.swift in Sources */,
206+
31420D342426BA4200A7B409 /* SDLoopingVideoView.SDVideo.swift in Sources */,
207+
31420D3A24270D0E00A7B409 /* DarkModeDemoViewController.swift in Sources */,
208+
31420D322426B3B600A7B409 /* SDLoopingVideoView.VideoExtension.swift in Sources */,
188209
31E948B42220CED000C6A14C /* SDLoopingVideoView.swift in Sources */,
189210
31E948A22220CE4400C6A14C /* AppDelegate.swift in Sources */,
190211
);
@@ -341,7 +362,7 @@
341362
);
342363
PRODUCT_BUNDLE_IDENTIFIER = "Solsma-Dev.SDLoopingVideoViewExample";
343364
PRODUCT_NAME = "$(TARGET_NAME)";
344-
SWIFT_VERSION = 4.2;
365+
SWIFT_VERSION = 5.0;
345366
TARGETED_DEVICE_FAMILY = "1,2";
346367
};
347368
name = Debug;
@@ -359,7 +380,7 @@
359380
);
360381
PRODUCT_BUNDLE_IDENTIFIER = "Solsma-Dev.SDLoopingVideoViewExample";
361382
PRODUCT_NAME = "$(TARGET_NAME)";
362-
SWIFT_VERSION = 4.2;
383+
SWIFT_VERSION = 5.0;
363384
TARGETED_DEVICE_FAMILY = "1,2";
364385
};
365386
name = Release;

0 commit comments

Comments
 (0)