247 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
			
		
		
	
	
			247 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
//
 | 
						|
//  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
 | 
						|
    @StateObject var recentContentViewModel = RecentContentViewModel()
 | 
						|
    
 | 
						|
    @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))
 | 
						|
                    .cancelOnDisappear(true)
 | 
						|
                    .downsampling(
 | 
						|
                        size: CGSize(
 | 
						|
                            width: screenSize().width - 26.7,
 | 
						|
                            height: screenSize().width - 26.7
 | 
						|
                        )
 | 
						|
                    )
 | 
						|
                    .resizable()
 | 
						|
                    .scaledToFill()
 | 
						|
                    .frame(
 | 
						|
                        width: screenSize().width - 26.7,
 | 
						|
                        height: screenSize().width - 26.7,
 | 
						|
                        alignment: .center
 | 
						|
                    )
 | 
						|
                    .cornerRadius(10.7, corners: [.topLeft, .topRight])
 | 
						|
                
 | 
						|
                HStack(alignment: .top) {
 | 
						|
                    Spacer()
 | 
						|
                    
 | 
						|
                    if audioContent.isAvailableUsePoint {
 | 
						|
                        Image("ic_point")
 | 
						|
                            .resizable()
 | 
						|
                            .aspectRatio(1, contentMode: .fit)
 | 
						|
                            .frame(width: 50)
 | 
						|
                            .padding(.trailing, 13.3)
 | 
						|
                            .padding(.top, 13.3)
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                .frame(
 | 
						|
                    width: screenSize().width - 26.7,
 | 
						|
                    height: screenSize().width - 26.7,
 | 
						|
                    alignment: .top
 | 
						|
                )
 | 
						|
                
 | 
						|
                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) {
 | 
						|
                    HStack(spacing: 24) {
 | 
						|
                        if !isAlertPreview {
 | 
						|
                            Image("ic_player_prev_10")
 | 
						|
                                .onTapGesture {
 | 
						|
                                    if isPlaying() {
 | 
						|
                                        contentPlayManager.seekBackward10Seconds()
 | 
						|
                                    }
 | 
						|
                                }
 | 
						|
                        }
 | 
						|
                        
 | 
						|
                        Image(isPlaying() ? "btn_audio_content_pause" : isAlertPreview ? "btn_audio_content_preview_play" : "btn_audio_content_play")
 | 
						|
                            .onTapGesture {
 | 
						|
                                ContentPlayerPlayManager.shared.resetPlayer()
 | 
						|
                                
 | 
						|
                                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
 | 
						|
                                    
 | 
						|
                                    recentContentViewModel.insertRecentContent(
 | 
						|
                                        contentId: Int64(audioContent.contentId),
 | 
						|
                                        coverImageUrl: audioContent.coverImageUrl,
 | 
						|
                                        title: audioContent.title,
 | 
						|
                                        creatorNickname: audioContent.creator.nickname
 | 
						|
                                    )
 | 
						|
                                }
 | 
						|
                            }
 | 
						|
                        
 | 
						|
                        if !isAlertPreview {
 | 
						|
                            Image("ic_player_next_10")
 | 
						|
                                .onTapGesture {
 | 
						|
                                    if isPlaying() {
 | 
						|
                                        contentPlayManager.seekForward10Seconds()
 | 
						|
                                    }
 | 
						|
                                }
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                } 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<Double> {
 | 
						|
        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()
 | 
						|
    }
 | 
						|
}
 |