sodalive-ios/SodaLive/Sources/Content/ContentPlayManager.swift

283 lines
8.8 KiB
Swift

//
// ContentPlayManager.swift
// SodaLive
//
// Created by klaus on 2023/08/11.
//
import Foundation
import AVKit
import MediaPlayer
import ObjectBox
final class ContentPlayManager: NSObject, ObservableObject {
static let shared = ContentPlayManager()
var creatorId = 0
@Published var contentId: Int = 0
@Published private (set) var duration: TimeInterval = 0
@Published var title = ""
@Published var nickname = ""
@Published var coverImage = ""
@Published var isFree: Bool? = nil
@Published var isPreview: Bool? = nil
@Published private (set) var isShowingMiniPlayer = false
@Published private (set) var isPlaying = false
@Published var isLoading = false
@Published var errorMessage = ""
@Published var isShowPopup = false
var player: AVAudioPlayer!
var startTimer: (() -> Void)?
var stopTimer: (() -> Void)?
private var playbackTrackingId: Id = 0
private let repository = PlaybackTrackingRepository()
}
extension ContentPlayManager {
func playAudio(
creatorId: Int = 0,
contentId: Int = 0,
title: String = "",
nickname: String = "",
coverImage: String = "",
contentUrl: String = "",
isFree: Bool? = nil,
isPreview: Bool? = nil
) {
if contentId <= 0 {
return
}
if let startTimer = startTimer {
startTimer()
}
if self.contentId > 0 && self.contentId == contentId {
player?.play()
isPlaying = player.isPlaying
} else {
isLoading = true
stopAudio()
self.creatorId = creatorId
self.contentId = contentId
self.title = title
self.nickname = nickname
self.coverImage = coverImage
self.isFree = isFree
self.isPreview = isPreview
guard let url = URL(string: contentUrl) else {
showError()
return
}
URLSession.shared.dataTask(with: url) { [unowned self] data, response, error in
guard let audioData = data else {
self.isLoading = false
return
}
do {
let audioSession = AVAudioSession.sharedInstance()
try audioSession.setCategory(.playback, mode: .default)
try audioSession.setActive(true)
self.player = try AVAudioPlayer(data: audioData)
saveNewPlaybackTracking(totalDuration: Int(player.duration), progress: 0)
DispatchQueue.main.async {
self.player?.volume = 1
self.player?.delegate = self
self.player?.prepareToPlay()
self.duration = self.player.duration
self.player?.play()
self.isPlaying = self.player.isPlaying
self.isShowingMiniPlayer = true
UIApplication.shared.beginReceivingRemoteControlEvents()
}
self.registerNowPlayingInfoCenter()
self.registerRemoteControlEvents()
} catch {
DispatchQueue.main.async {
self.showError()
}
}
DispatchQueue.main.async {
self.isLoading = false
}
}.resume()
}
}
func stopAudio() {
if let player = player {
player.stop()
setEndPositionPlaybackTracking(progress: Int(player.currentTime))
player.currentTime = 0
isPlaying = player.isPlaying
}
resetAudioData()
unRegisterRemoteControlEvents()
}
func conditionalStopAudio(contentId: Int) {
if self.contentId == contentId {
stopAudio()
}
}
func pauseAudio() {
if let player = player {
player.pause()
isPlaying = player.isPlaying
if let stopTimer = stopTimer {
stopTimer()
}
}
}
func resetAudioData() {
title = ""
nickname = ""
coverImage = ""
contentId = 0
duration = 0
isPreview = false
isShowingMiniPlayer = false
player = nil
startTimer = nil
stopTimer = nil
}
func setCurrentTime(_ progress: TimeInterval) {
if let player = player, contentId > 0 {
player.currentTime = progress
saveNewPlaybackTracking(totalDuration: Int(player.duration), progress: Int(progress))
}
}
private func repeatAudio() {
if let stopTimer = stopTimer {
stopTimer()
}
player.stop()
setEndPositionPlaybackTracking(progress: Int(player.currentTime))
player.currentTime = 0
saveNewPlaybackTracking(totalDuration: Int(player.duration), progress: 0)
player.play()
if let startTimer = startTimer {
startTimer()
}
}
private func showError() {
self.errorMessage = "오류가 발생했습니다. 다시 시도해 주세요."
self.isShowPopup = true
self.resetAudioData()
}
private func registerNowPlayingInfoCenter() {
let center = MPNowPlayingInfoCenter.default()
var nowPlayingInfo = center.nowPlayingInfo ?? [String: Any]()
nowPlayingInfo[MPMediaItemPropertyTitle] = title
nowPlayingInfo[MPMediaItemPropertyArtist] = nickname
if let artworkURL = URL(string: coverImage), let imageData = try? Data(contentsOf: artworkURL), let artworkImage = UIImage(data: imageData) {
let artwork = MPMediaItemArtwork(boundsSize: artworkImage.size) { size in
return artworkImage
}
nowPlayingInfo[MPMediaItemPropertyArtwork] = artwork
}
//
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = player.duration
// progressBar
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player.rate
//
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player.currentTime
center.nowPlayingInfo = nowPlayingInfo
}
private func registerRemoteControlEvents() {
let center = MPRemoteCommandCenter.shared()
center.playCommand.addTarget { [unowned self] (commandEvent) -> MPRemoteCommandHandlerStatus in
if let player = player {
player.play()
self.isPlaying = player.isPlaying
if let startTimer = self.startTimer {
startTimer()
}
}
return .success
}
center.pauseCommand.addTarget { [unowned self] (commandEvent) -> MPRemoteCommandHandlerStatus in
self.pauseAudio()
return .success
}
}
private func unRegisterRemoteControlEvents() {
let center = MPRemoteCommandCenter.shared()
center.playCommand.removeTarget(nil)
center.pauseCommand.removeTarget(nil)
UIApplication.shared.endReceivingRemoteControlEvents()
}
}
extension ContentPlayManager {
private func saveNewPlaybackTracking(totalDuration: Int, progress: Int) {
if creatorId != UserDefaults.int(forKey: .userId) {
playbackTrackingId = repository
.savePlaybackTracking(data: PlaybackTracking(
audioContentId: contentId,
totalDuration: totalDuration,
startPosition: progress,
isFree: isFree ?? true,
isPreview: isPreview ?? true)
)
}
}
private func setEndPositionPlaybackTracking(progress: Int) {
if creatorId != UserDefaults.int(forKey: .userId) && playbackTrackingId > 0 {
if let playbackTracking = repository.getPlaybackTracking(id: playbackTrackingId) {
playbackTracking.endPosition = progress
_ = repository.savePlaybackTracking(data: playbackTracking)
}
playbackTrackingId = 0
}
}
}
extension ContentPlayManager: AVAudioPlayerDelegate {
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
if UserDefaults.bool(forKey: .isContentPlayLoop) {
repeatAudio()
} else {
stopAudio()
}
}
}