Move all player ui elements and actions to PlayerVC
MODIFY
Podcast.xcodeproj/project.pbxproj
Podcast.xcodeproj/project.pbxproj
/* Begin PBXBuildFile section */
421501271D9F5FE1002839D1 /* Player.swift in Sources */ = {isa = PBXBuildFile; fileRef = 421501261D9F5FE1002839D1 /* Player.swift */; };
- 42AEE2521D8A08AA001C4786 /* PlayerControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42AEE2511D8A08AA001C4786 /* PlayerControlsView.swift */; };
42D7D98D1D80C557000691A4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42D7D98C1D80C557000691A4 /* AppDelegate.swift */; };
42D7D9921D80C557000691A4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 42D7D9901D80C557000691A4 /* Main.storyboard */; };
42D7D9941D80C557000691A4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 42D7D9931D80C557000691A4 /* Assets.xcassets */; };
/* Begin PBXFileReference section */
13C8D9A7A7FB0C3D27C81500 /* Pods-PodcastTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PodcastTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PodcastTests/Pods-PodcastTests.debug.xcconfig"; sourceTree = "<group>"; };
421501261D9F5FE1002839D1 /* Player.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Player.swift; sourceTree = "<group>"; };
- 42AEE2511D8A08AA001C4786 /* PlayerControlsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerControlsView.swift; sourceTree = "<group>"; };
42D7D9891D80C557000691A4 /* Podcast.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Podcast.app; sourceTree = BUILT_PRODUCTS_DIR; };
42D7D98C1D80C557000691A4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
42D7D9911D80C557000691A4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
42F684EC1D80CC9F0035217A /* DiscoverViewController.swift */,
7F9D0E8F1D85029C00D2BC1C /* Podcast-Bridging-Header.h */,
7F9D0E961D85A37700D2BC1C /* LoginViewController.swift */,
- 42AEE2511D8A08AA001C4786 /* PlayerControlsView.swift */,
D300C0911DAEF801000CDBF5 /* CategoriesFeedViewController.swift */,
D37D799F1DEF920000D752F0 /* SearchViewController.swift */,
);
7F97FBDE1D8663000073FC0F /* REST.swift in Sources */,
D393D7551D8F0D0C00241460 /* DiscoverTableViewCell.swift in Sources */,
421501271D9F5FE1002839D1 /* Player.swift in Sources */,
- 42AEE2521D8A08AA001C4786 /* PlayerControlsView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

MODIFY
Podcast/PlayerViewController.swift
Podcast/PlayerViewController.swift
//
import UIKit
+import CoreMedia
-class PlayerViewController: UIViewController {
+class PlayerViewController: UIViewController, PlayerDelegate {
// Mark: Constants
let PlayerControlPanelHeight: CGFloat = 182
+ let ForwardsBackwardsButtonSize: CGFloat = 44
+ let PlayPauseButtonSize: CGFloat = 40
+ let SliderHeight: CGFloat = 6
+ let SliderInset: CGFloat = 48
+ let SliderCenterY: CGFloat = 78
+ let EpisodeNameLabelYValue: CGFloat = 21
+ let SeriesNameLabelYValue: CGFloat = 40
+ let TimeLabelHorizontalInset: CGFloat = 14
+ let ControlButtonsCenterY: CGFloat = 120
+ let ControlButtonsCenterHorizontalOffset: CGFloat = 55
// Mark: Properties
- var controlsView: PlayerControlsView!
+ var controlsView: UIView!
var artworkImageView: UIImageView!
+ var episodeNameLabel: UILabel!
+ var seriesNameLabel: UILabel!
+ var slider: UISlider!
+ var playPauseButton: UIButton!
+ var forwardsButton: UIButton!
+ var forwardsLabel: UILabel!
+ var backwardsButton: UIButton!
+ var backwardsLabel: UILabel!
+ var rightTimeLabel: UILabel!
+ var leftTimeLabel: UILabel!
+ var isScrubbing = false
+
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .podcastGrayLight
tabBarController?.title = "Now Playing"
- controlsView = PlayerControlsView(frame: CGRect(x: 0, y: view.frame.height - PlayerControlPanelHeight - tabBarController!.tabBar.frame.height, width: view.frame.width, height: PlayerControlPanelHeight))
- view.addSubview(controlsView)
-
artworkImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height - PlayerControlPanelHeight - tabBarController!.tabBar.frame.height))
artworkImageView.contentMode = .scaleAspectFill
artworkImageView.image = UIImage(named: "SampleSeriesArtwork")
view.addSubview(artworkImageView)
+
+ controlsView = UIView(frame: CGRect(x: 0, y: view.frame.height - PlayerControlPanelHeight - tabBarController!.tabBar.frame.height, width: view.frame.width, height: PlayerControlPanelHeight))
+ controlsView.backgroundColor = .white
+ view.addSubview(controlsView)
+
+ episodeNameLabel = UILabel(frame: .zero)
+ episodeNameLabel.textAlignment = .center
+ episodeNameLabel.font = .boldSystemFont(ofSize: 15)
+ episodeNameLabel.text = "Episode Name"
+ episodeNameLabel.sizeToFit()
+ episodeNameLabel.center = CGPoint(x: view.frame.width/2, y: EpisodeNameLabelYValue)
+ controlsView.addSubview(episodeNameLabel)
+
+ seriesNameLabel = UILabel(frame: .zero)
+ seriesNameLabel.textAlignment = .center
+ seriesNameLabel.font = .systemFont(ofSize: 13)
+ seriesNameLabel.text = "Series Name"
+ seriesNameLabel.sizeToFit()
+ seriesNameLabel.center = CGPoint(x: view.frame.width/2, y: SeriesNameLabelYValue)
+ controlsView.addSubview(seriesNameLabel)
+
+ slider = UISlider(frame: .zero)
+ slider.thumbTintColor = .podcastGreenBlue
+ slider.minimumTrackTintColor = .podcastBlueLight
+ slider.maximumTrackTintColor = .podcastBlueLight
+ slider.addTarget(self, action: #selector(sliderValueChanged), for: .valueChanged)
+ slider.addTarget(self, action: #selector(endScrubbing), for: .touchUpInside)
+ slider.addTarget(self, action: #selector(endScrubbing), for: .touchUpOutside)
+ slider.frame = CGRect(x: 0, y: 0, width: view.frame.width - (2 * SliderInset), height: SliderHeight)
+ slider.center = CGPoint(x: view.frame.width/2, y: SliderCenterY)
+ controlsView.addSubview(slider)
+
+ leftTimeLabel = UILabel(frame: .zero)
+ leftTimeLabel.font = .systemFont(ofSize: 12)
+ leftTimeLabel.textAlignment = .center
+ leftTimeLabel.text = "-:--"
+ leftTimeLabel.sizeToFit()
+ leftTimeLabel.center = CGPoint(x: TimeLabelHorizontalInset + leftTimeLabel.frame.width/2, y: SliderCenterY)
+ controlsView.addSubview(leftTimeLabel)
+
+ rightTimeLabel = UILabel(frame: .zero)
+ rightTimeLabel.font = .systemFont(ofSize: 12)
+ rightTimeLabel.textAlignment = .center
+ rightTimeLabel.text = "-:--"
+ rightTimeLabel.sizeToFit()
+ rightTimeLabel.center = CGPoint(x: view.frame.width - rightTimeLabel.frame.width/2 - TimeLabelHorizontalInset, y: SliderCenterY)
+ controlsView.addSubview(rightTimeLabel)
+
+ playPauseButton = UIButton(frame: .zero)
+ playPauseButton.setBackgroundImage(#imageLiteral(resourceName: "Play"), for: .normal)
+ playPauseButton.adjustsImageWhenHighlighted = false
+ playPauseButton.addTarget(self, action: #selector(playPauseButtonPress), for: .touchUpInside)
+ playPauseButton.frame = CGRect(x: 0, y: 0, width: PlayPauseButtonSize, height: PlayPauseButtonSize)
+ playPauseButton.center = CGPoint(x: view.frame.width/2, y: ControlButtonsCenterY)
+ controlsView.addSubview(playPauseButton)
+
+ forwardsButton = UIButton(frame: .zero)
+ forwardsButton.setBackgroundImage(#imageLiteral(resourceName: "Forwards"), for: .normal)
+ forwardsButton.adjustsImageWhenHighlighted = false
+ forwardsButton.addTarget(self, action: #selector(forwardButtonPress), for: .touchUpInside)
+ forwardsButton.frame = CGRect(x: 0, y: 0, width: ForwardsBackwardsButtonSize, height: ForwardsBackwardsButtonSize)
+ forwardsButton.center = CGPoint(x: playPauseButton.frame.maxX + ControlButtonsCenterHorizontalOffset, y: ControlButtonsCenterY)
+ controlsView.addSubview(forwardsButton)
+
+ forwardsLabel = UILabel(frame: .zero)
+ forwardsLabel.text = "30"
+ forwardsLabel.sizeToFit()
+ forwardsLabel.center = forwardsButton.center
+ controlsView.addSubview(forwardsLabel)
+
+ backwardsButton = UIButton(frame: .zero)
+ backwardsButton.setBackgroundImage(#imageLiteral(resourceName: "Backwards"), for: .normal)
+ backwardsButton.adjustsImageWhenHighlighted = false
+ backwardsButton.addTarget(self, action: #selector(backwardButtonPress), for: .touchUpInside)
+ backwardsButton.frame = CGRect(x: 0, y: 0, width: ForwardsBackwardsButtonSize, height: ForwardsBackwardsButtonSize)
+ backwardsButton.center = CGPoint(x: playPauseButton.frame.minX - ControlButtonsCenterHorizontalOffset, y: ControlButtonsCenterY)
+ controlsView.addSubview(backwardsButton)
+
+ backwardsLabel = UILabel(frame: .zero)
+ backwardsLabel.text = "15"
+ backwardsLabel.sizeToFit()
+ backwardsLabel.center = backwardsButton.center
+ controlsView.addSubview(backwardsLabel)
+
+ Player.sharedInstance.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
- controlsView.playerDidUpdateTime()
- controlsView.playerDidChangeState()
+ playerDidUpdateTime()
+ playerDidChangeState()
+ }
+
+ func endScrubbing() {
+ Player.sharedInstance.setProgress(progress: slider.value)
+ isScrubbing = false
+ }
+
+ func sliderValueChanged() {
+ isScrubbing = true
+
+ // update time labels to be the slider value
+ guard let duration = Player.sharedInstance.getDuration() else { return }
+
+ let newSeconds = duration.seconds * Double(slider.value)
+ let newTimePassed = CMTime(seconds: newSeconds, preferredTimescale: Int32(NSEC_PER_SEC))
+ let newTimeLeft = CMTimeSubtract(duration, newTimePassed)
+
+ leftTimeLabel.text = newTimePassed.durationText
+ leftTimeLabel.sizeToFit()
+ rightTimeLabel.text = newTimeLeft.durationText
+ rightTimeLabel.sizeToFit()
+ }
+
+
+ func playPauseButtonPress() {
+ Player.sharedInstance.togglePlaying()
+ }
+
+ func forwardButtonPress() {
+ Player.sharedInstance.skip(seconds: 30.0)
+ }
+
+ func backwardButtonPress() {
+ Player.sharedInstance.skip(seconds: -15.0)
+ }
+
+ // Mark: PlayerDelegate
+
+ func playerDidUpdateTime() {
+ // don't update slider value and time labels if currently scrubbing
+ if isScrubbing { return }
+
+ slider.value = Player.sharedInstance.getProgress()
+
+ if let timePassed = Player.sharedInstance.getTimePassed() {
+ leftTimeLabel.text = timePassed.durationText
+ } else {
+ leftTimeLabel.text = "-:--"
+ }
+
+ if let timeLeft = Player.sharedInstance.getTimeLeft() {
+ rightTimeLabel.text = timeLeft.durationText
+ } else {
+ rightTimeLabel.text = "-:--"
+ }
+
+ leftTimeLabel.sizeToFit()
+ rightTimeLabel.sizeToFit()
+ }
+
+ func playerDidChangeState() {
+ if Player.sharedInstance.shouldDisplayPlayButton() {
+ playPauseButton.setBackgroundImage(#imageLiteral(resourceName: "Play"), for: .normal)
+ } else {
+ playPauseButton.setBackgroundImage(#imageLiteral(resourceName: "Pause"), for: .normal)
+ }
}
}

Move all player ui elements and actions to PlayerVC