// // 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: .moviePlayback) 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() } } }