Files
AudioStreaming/AudioExample/AudioExample/Controllers/PlayerControlsViewModel.swift
T
Dimitris C 7e45a7b2f5 Removes unneeded DispatchTimerSource (#32)
Correct Spelling issues
Fix an issue in RemoteAudioSource when icycast headers are not available
2021-10-05 18:32:55 +03:00

167 lines
4.7 KiB
Swift

//
// PlayerControlsViewModel.swift
// AudioExample
//
// Created by Dimitrios Chatzieleftheriou on 14/11/2020.
// Copyright © 2020 Dimitrios Chatzieleftheriou. All rights reserved.
//
import AudioStreaming
import Foundation
import UIKit
enum SeekAction: Equatable {
case started
case updateSeek(time: Float)
case ended
}
enum ControlsEffects {
case updateMuteButton(String)
case updatePauseResumeButton(String)
case updateSliderMinMaxValue(min: Float, max: Float)
case updateSliderValue(value: Float)
case updateMetadata(String)
}
final class PlayerControlsViewModel {
var updateContent: ((ControlsEffects) -> Void)?
var updateProgressAndDurationTitles: ((String, String) -> Void)?
private let playerService: AudioPlayerService
private var displayLink: CADisplayLink?
private var seekTime: Float = 0
private var isScrubbing: Bool = false
let rateMinValue: Float = 1.0
let rateMaxValue: Float = 3.0
var currentRateTitle: String {
String(format: "%.1fx", playerService.rate)
}
init(playerService: AudioPlayerService) {
self.playerService = playerService
self.playerService.delegate.add(delegate: self)
}
func stop() {
playerService.stop()
stopDisplayLink(resetLabels: true)
updateContent?(.updatePauseResumeButton("Pause"))
}
func togglePauseResume() {
playerService.toggle()
let isPaused = playerService.state == .paused
updateContent?(.updatePauseResumeButton(isPaused ? "Resume" : "Pause"))
}
func toggleMute() {
playerService.toggleMute()
let isMuted = playerService.isMuted
updateContent?(.updateMuteButton(isMuted ? "Unmute" : "Mute"))
}
func seek(action: SeekAction) {
switch action {
case .started:
isScrubbing = true
seekTime = 0
case let .updateSeek(time):
seekTime = time
case .ended:
isScrubbing = false
if playerService.duration > 0 {
playerService.seek(at: seekTime)
}
}
}
func update(rate: Float, updater: (Float) -> Void) {
let rate = round(rate / 0.5) * 0.5
playerService.update(rate: rate)
updater(rate)
}
private func startDisplayLink() {
displayLink?.invalidate()
displayLink = nil
displayLink = UIScreen.main.displayLink(withTarget: self, selector: #selector(tick))
displayLink?.preferredFramesPerSecond = 6
displayLink?.add(to: .current, forMode: .common)
}
private func stopDisplayLink(resetLabels: Bool) {
displayLink?.invalidate()
displayLink = nil
if resetLabels {
resetLabelsAndSlider()
}
}
@objc private func tick() {
let duration = playerService.duration
let progress = playerService.progress
if duration > 0 {
let elapsed = Int(progress)
let remaining = Int(duration - progress)
updateContent?(.updateSliderMinMaxValue(min: 0.0, max: Float(duration)))
if !isScrubbing {
updateContent?(.updateSliderValue(value: Float(progress)))
}
updateProgressAndDurationTitles?(timeFrom(seconds: elapsed), timeFrom(seconds: remaining))
} else {
let elapsed = Int(progress)
updateProgressAndDurationTitles?("Live broadcast", timeFrom(seconds: elapsed))
}
}
private func resetLabelsAndSlider() {
updateProgressAndDurationTitles?("--:--", "--:--")
updateContent?(.updateSliderMinMaxValue(min: 0, max: 0))
updateContent?(.updateSliderValue(value: 0))
}
private func timeFrom(seconds: Int) -> String {
let correctSeconds = seconds % 60
let minutes = (seconds / 60) % 60
let hours = seconds / 3600
if hours > 0 {
return String(format: "%02d:%02d:%02d", hours, minutes, correctSeconds)
}
return String(format: "%02d:%02d", minutes, correctSeconds)
}
}
extension PlayerControlsViewModel: AudioPlayerServiceDelegate {
func didStopPlaying() {
stopDisplayLink(resetLabels: true)
updateContent?(.updateMetadata(""))
}
func statusChanged(status _: AudioPlayerState) {}
func didStartPlaying() {
startDisplayLink()
resetLabelsAndSlider()
updateContent?(.updateMetadata(""))
}
func errorOccurred(error _: AudioPlayerError) {}
func metadataReceived(metadata: [String: String]) {
guard !metadata.isEmpty else { return }
if let title = metadata["StreamTitle"] {
updateContent?(.updateMetadata("Now Playing: \(title)"))
} else {
updateContent?(.updateMetadata(""))
}
}
}