// // ContentDetailPlayView.swift // SodaLive // // Created by klaus on 2023/08/13. // import SwiftUI import Kingfisher import Sliders struct ContentDetailPlayView: View { let audioContent: GetAudioContentDetailResponse let isAlertPreview: Bool @Binding var isShowPreviewAlert: Bool @StateObject var contentPlayManager = ContentPlayManager.shared @State private var isRepeat = UserDefaults.bool(forKey: .isContentPlayLoop) @State private var isEditing = false @State private var progress: TimeInterval = 0 @State private var timer = Timer.publish(every: 0.01, on: .main, in: .common).autoconnect() var body: some View { VStack(alignment: .leading, spacing: 8) { ZStack { KFImage(URL(string: audioContent.coverImageUrl)) .resizable() .scaledToFill() .frame( width: screenSize().width - 26.7, height: screenSize().width - 26.7, alignment: .center ) .cornerRadius(10.7, corners: [.topLeft, .topRight]) if let _ = audioContent.totalContentCount, let remainingContentCount = audioContent.remainingContentCount, remainingContentCount <= 0, audioContent.creator.creatorId != UserDefaults.int(forKey: .userId), !audioContent.existOrdered { Text("Sold Out") .font(.custom(Font.bold.rawValue, size: 36.7)) .foregroundColor(.white) .frame( width: screenSize().width - 26.7, height: screenSize().width - 26.7, alignment: .center ) .background(Color.black.opacity(0.6)) } else if audioContent.releaseDate == nil && !isAlertPreview || (audioContent.isActivePreview && !audioContent.contentUrl.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) { Image(isPlaying() ? "btn_audio_content_pause" : isAlertPreview ? "btn_audio_content_preview_play" : "btn_audio_content_play") .onTapGesture { if isPlaying() { contentPlayManager.pauseAudio() } else { contentPlayManager.startTimer = startTimer contentPlayManager.stopTimer = stopTimer contentPlayManager.playAudio( contentId: audioContent.contentId, title: audioContent.title, nickname: audioContent.creator.nickname, coverImage: audioContent.coverImageUrl, contentUrl: audioContent.contentUrl, isFree: audioContent.price <= 0, isPreview: !audioContent.existOrdered && audioContent.price > 0 ) isShowPreviewAlert = true } } } else if audioContent.releaseDate == nil { Text("해당 콘텐츠는 크리에이터의 요청으로\n미리듣기를 제공하지 않습니다.") .font(.custom(Font.medium.rawValue, size: 16.7)) .foregroundColor(Color.grayee) .padding(13.3) .background(Color.gray33.opacity(0.5)) .cornerRadius(46.7) } VStack(alignment: .leading, spacing: 13.3) { Spacer() ValueSlider( value: audioContent.contentId == contentPlayManager.contentId ? $progress : .constant(0), in: sliderRange(), onEditingChanged: { editing in isEditing = editing if !editing { contentPlayManager.setCurrentTime(progress) } } ) .valueSliderStyle( HorizontalValueSliderStyle( track: HorizontalValueTrack( view: Rectangle().foregroundColor(Color.button), mask: Rectangle() ) .background(Rectangle().foregroundColor(Color.gray97.opacity(0.3))) .frame(height: 5.3), thumbSize: CGSizeZero, options: .interactiveTrack ) ) .frame(height: 5.3) } if contentPlayManager.isLoading { LoadingView() } } .frame( width: screenSize().width - 26.7, height: screenSize().width - 26.7 ) HStack(spacing: 0) { Text("\(getProgress()) / \(getDuration())") .font(.custom(Font.medium.rawValue, size: 14.7)) .foregroundColor(.white) Spacer() Image( isRepeat ? "btn_player_repeat" : "btn_player_repeat_done" ) .onTapGesture { isRepeat = !UserDefaults.bool(forKey: .isContentPlayLoop) UserDefaults.set( isRepeat, forKey: .isContentPlayLoop ) } } .frame(width: screenSize().width - 40) } .onAppear { if !isPlaying() { stopTimer() } } .onReceive(timer) { _ in guard let player = contentPlayManager.player, !isEditing else { return } self.progress = player.currentTime } } private func isPlaying() -> Bool { return contentPlayManager.contentId == audioContent.contentId && contentPlayManager.isPlaying } private func sliderRange() -> ClosedRange { if audioContent.contentId == contentPlayManager.contentId { return 0...contentPlayManager.duration } else { return 0...0 } } private func getProgress() -> String { if audioContent.contentId == contentPlayManager.contentId { return secondsToMinutesSeconds(seconds: Int(progress)) } else { return secondsToMinutesSeconds(seconds: 0) } } private func getDuration() -> String { if audioContent.contentId == contentPlayManager.contentId { return secondsToMinutesSeconds(seconds: Int(contentPlayManager.duration)) } else { return audioContent.duration } } private func secondsToMinutesSeconds(seconds: Int) -> String { let hours = String(format: "%02d", seconds / 3600) let minute = String(format: "%02d", (seconds % 3600) / 60) let second = String(format: "%02d", seconds % 60) return "\(hours):\(minute):\(second)" } private func startTimer() { timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect() } private func stopTimer() { timer.upstream.connect().cancel() } }