7e45a7b2f5
Correct Spelling issues Fix an issue in RemoteAudioSource when icycast headers are not available
167 lines
4.7 KiB
Swift
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(""))
|
|
}
|
|
}
|
|
}
|