Files
AudioStreaming/AudioExample/AudioExample/Controllers/PlayerControlsViewController.swift
T
2020-11-15 20:14:59 +00:00

214 lines
9.0 KiB
Swift

//
// PlayerControlsViewController.swift
// AudioExample
//
// Created by Dimitrios Chatzieleftheriou on 14/11/2020.
// Copyright © 2020 Dimitrios Chatzieleftheriou. All rights reserved.
//
import UIKit
class PlayerControlsViewController: UIViewController {
private lazy var resumeButton = UIButton()
private lazy var stopButton = UIButton(type: .custom)
private lazy var muteButton = UIButton()
private lazy var slider = UISlider()
private lazy var elapsedPlayTimeLabel = UILabel()
private lazy var remainingPlayTimeLabel = UILabel()
private lazy var rateSlider = UISlider()
private lazy var rateSliderValueLabel = UILabel()
private lazy var playerStatus = UILabel()
private let viewModel: PlayerControlsViewModel
init(viewModel: PlayerControlsViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
setupUI()
setupBinding()
}
private func setupUI() {
muteButton.translatesAutoresizingMaskIntoConstraints = false
muteButton.setTitle("Mute", for: .normal)
muteButton.setTitleColor(.label, for: .normal)
muteButton.setTitleColor(.secondaryLabel, for: .highlighted)
muteButton.setTitleColor(.tertiaryLabel, for: .disabled)
muteButton.accessibilityIdentifier = "muteButton"
muteButton.addTarget(self, action: #selector(toggleMute), for: .touchUpInside)
resumeButton.translatesAutoresizingMaskIntoConstraints = false
resumeButton.setTitle("Pause", for: .normal)
resumeButton.accessibilityIdentifier = "resumeButton"
resumeButton.setTitleColor(.label, for: .normal)
resumeButton.setTitleColor(.secondaryLabel, for: .highlighted)
resumeButton.setTitleColor(.tertiaryLabel, for: .disabled)
resumeButton.addTarget(self, action: #selector(pauseResume), for: .touchUpInside)
stopButton.translatesAutoresizingMaskIntoConstraints = false
stopButton.setTitle("Stop", for: .normal)
stopButton.setTitleColor(.label, for: .normal)
stopButton.setTitleColor(.secondaryLabel, for: .highlighted)
stopButton.setTitleColor(.tertiaryLabel, for: .disabled)
stopButton.accessibilityIdentifier = "stopButton"
stopButton.addTarget(self, action: #selector(stop), for: .touchUpInside)
let controlsStackView = UIStackView(arrangedSubviews: [resumeButton, stopButton, muteButton])
controlsStackView.translatesAutoresizingMaskIntoConstraints = false
controlsStackView.axis = .horizontal
controlsStackView.distribution = .fillEqually
controlsStackView.alignment = .center
controlsStackView.accessibilityIdentifier = "controlsStackView"
slider.translatesAutoresizingMaskIntoConstraints = false
slider.accessibilityIdentifier = "slider"
slider.tintColor = .systemGray2
slider.thumbTintColor = .systemGray
slider.isContinuous = true
slider.semanticContentAttribute = .playback
slider.addTarget(self, action: #selector(sliderTouchedDown), for: .touchDown)
slider.addTarget(self, action: #selector(sliderTouchedUp), for: [.touchUpInside, .touchUpOutside])
slider.addTarget(self, action: #selector(sliderValueChanged), for: .valueChanged)
elapsedPlayTimeLabel.text = "--:--"
elapsedPlayTimeLabel.accessibilityIdentifier = "elapsedPlayTimeLabel"
elapsedPlayTimeLabel.translatesAutoresizingMaskIntoConstraints = false
elapsedPlayTimeLabel.font = UIFont.preferredFont(forTextStyle: .footnote)
elapsedPlayTimeLabel.textAlignment = .left
remainingPlayTimeLabel.text = "--:--"
remainingPlayTimeLabel.accessibilityIdentifier = "remainingPlayTimeLabel"
remainingPlayTimeLabel.translatesAutoresizingMaskIntoConstraints = false
remainingPlayTimeLabel.font = UIFont.preferredFont(forTextStyle: .footnote)
remainingPlayTimeLabel.textAlignment = .right
let playbackTimeLabelsStack = UIStackView(arrangedSubviews: [elapsedPlayTimeLabel, remainingPlayTimeLabel])
playbackTimeLabelsStack.translatesAutoresizingMaskIntoConstraints = false
playbackTimeLabelsStack.axis = .horizontal
playbackTimeLabelsStack.distribution = .fillEqually
playbackTimeLabelsStack.accessibilityIdentifier = "playbackTimeLabelsStack"
playerStatus.text = ""
playerStatus.translatesAutoresizingMaskIntoConstraints = false
playerStatus.numberOfLines = 0
playerStatus.accessibilityIdentifier = "playerStatus-label"
let sliderLabel = UILabel()
sliderLabel.translatesAutoresizingMaskIntoConstraints = false
sliderLabel.text = "Rate: "
rateSliderValueLabel.translatesAutoresizingMaskIntoConstraints = false
rateSliderValueLabel.text = viewModel.currentRateTitle
rateSlider.translatesAutoresizingMaskIntoConstraints = false
rateSlider.minimumValue = viewModel.rateMinValue
rateSlider.maximumValue = viewModel.rateMaxValue
rateSlider.value = viewModel.rateMinValue
rateSlider.addTarget(self, action: #selector(rateValueChanged), for: .valueChanged)
let sliderWarningLabel = UILabel()
sliderWarningLabel.translatesAutoresizingMaskIntoConstraints = false
sliderWarningLabel.text = "Adjusting rate in live broadcast is not recommended"
sliderWarningLabel.numberOfLines = 2
sliderWarningLabel.textColor = .systemRed
let rateSliderStackView = UIStackView(arrangedSubviews: [sliderLabel, rateSlider, rateSliderValueLabel])
rateSliderStackView.spacing = 10
rateSliderStackView.axis = .horizontal
let controlsAndSliderStack = UIStackView(arrangedSubviews: [controlsStackView,
slider,
playbackTimeLabelsStack,
playerStatus,
rateSliderStackView,
sliderWarningLabel])
controlsAndSliderStack.translatesAutoresizingMaskIntoConstraints = false
controlsAndSliderStack.spacing = 10
controlsAndSliderStack.setCustomSpacing(15, after: playbackTimeLabelsStack)
controlsAndSliderStack.axis = .vertical
controlsAndSliderStack.distribution = .fill
controlsAndSliderStack.alignment = .fill
controlsAndSliderStack.isLayoutMarginsRelativeArrangement = true
controlsAndSliderStack.layoutMargins = .init(top: 15, left: 10, bottom: 0, right: 10)
controlsAndSliderStack.accessibilityIdentifier = "controlsAndSliderStack"
view.addSubview(controlsAndSliderStack)
view.accessibilityIdentifier = "controller-view"
NSLayoutConstraint.activate([
controlsAndSliderStack.topAnchor.constraint(equalTo: view.topAnchor),
controlsAndSliderStack.leadingAnchor.constraint(equalTo: view.leadingAnchor),
controlsAndSliderStack.trailingAnchor.constraint(equalTo: view.trailingAnchor),
])
}
private func setupBinding() {
viewModel.updateContent = { [unowned self] effect in
switch effect {
case let .updateMuteButton(title):
self.muteButton.setTitle(title, for: .normal)
case let .updatePauseResumeButton(title):
self.resumeButton.setTitle(title, for: .normal)
case let .updateSliderMinMaxValue(min, max):
self.slider.minimumValue = min
self.slider.maximumValue = max
case let .updateSliderValue(value):
self.slider.value = value
case let .updateMetadata(title):
self.playerStatus.text = title
}
}
viewModel.updateProgressAndDurationTitles = { [elapsedPlayTimeLabel, remainingPlayTimeLabel] progress, duration in
elapsedPlayTimeLabel.text = progress
remainingPlayTimeLabel.text = duration
}
}
@objc private func rateValueChanged() {
viewModel.update(rate: rateSlider.value) { [rateSlider] value in
rateSlider.value = value
}
rateSliderValueLabel.text = viewModel.currentRateTitle
}
@objc private func toggleMute() {
viewModel.toggleMute()
}
@objc private func pauseResume() {
viewModel.togglePauseResume()
}
@objc private func stop() {
viewModel.stop()
}
@objc
func sliderTouchedDown() {
viewModel.seek(action: .started)
}
@objc
func sliderTouchedUp() {
viewModel.seek(action: .ended)
}
@objc
func sliderValueChanged() {
viewModel.seek(action: .updateSeek(time: slider.value))
}
}