오디션 - 오디오 재생기능 추가
This commit is contained in:
parent
7e13689763
commit
dd6c2fe469
|
@ -13,6 +13,8 @@ struct AuditionApplicantItemView: View {
|
||||||
let item: GetAuditionRoleApplicantItem
|
let item: GetAuditionRoleApplicantItem
|
||||||
let onClickVote: (Int) -> Void
|
let onClickVote: (Int) -> Void
|
||||||
|
|
||||||
|
@StateObject var soundManager = AuditionSoundManager.shared
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 5.3) {
|
VStack(spacing: 5.3) {
|
||||||
HStack(spacing: 13.3) {
|
HStack(spacing: 13.3) {
|
||||||
|
@ -26,8 +28,12 @@ struct AuditionApplicantItemView: View {
|
||||||
.frame(width: 40, height: 40)
|
.frame(width: 40, height: 40)
|
||||||
.clipShape(Circle())
|
.clipShape(Circle())
|
||||||
|
|
||||||
Image("ic_audition_play")
|
Image(soundManager.applicantId == item.applicantId && soundManager.isPlaying ? "ic_audition_pause" : "ic_audition_play")
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
|
soundManager.playOrPause(
|
||||||
|
applicantId: item.applicantId,
|
||||||
|
url: item.voiceUrl
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +44,17 @@ struct AuditionApplicantItemView: View {
|
||||||
.foregroundColor(Color.white)
|
.foregroundColor(Color.white)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
if soundManager.applicantId == item.applicantId {
|
||||||
|
Text("\(secondsToMinutesSeconds(Int(soundManager.currentTime)))/\(secondsToMinutesSeconds(Int(soundManager.duration)))")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
|
.foregroundColor(Color.gray77)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if soundManager.applicantId == item.applicantId {
|
||||||
|
ProgressView(value: soundManager.currentTime, total: soundManager.duration)
|
||||||
|
.progressViewStyle(LinearProgressViewStyle(tint: Color.button))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +77,13 @@ struct AuditionApplicantItemView: View {
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 13.3)
|
.padding(.horizontal, 13.3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func secondsToMinutesSeconds(_ seconds: Int) -> String {
|
||||||
|
let minute = String(format: "%02d", seconds / 60)
|
||||||
|
let second = String(format: "%02d", seconds % 60)
|
||||||
|
|
||||||
|
return "\(minute):\(second)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
//
|
||||||
|
// AuditionSoundManager.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 1/7/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import AVKit
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
class AuditionSoundManager: NSObject, ObservableObject {
|
||||||
|
static let shared = AuditionSoundManager()
|
||||||
|
|
||||||
|
@Published private (set) var isPlaying = false
|
||||||
|
|
||||||
|
@Published var isLoading = false
|
||||||
|
@Published var errorMessage = ""
|
||||||
|
@Published var isShowPopup = false
|
||||||
|
|
||||||
|
@Published var currentTime: Double = 0.0
|
||||||
|
@Published private (set) var duration: Double = 0.0
|
||||||
|
|
||||||
|
private var player: AVAudioPlayer!
|
||||||
|
private var timer: Timer?
|
||||||
|
@Published private (set) var applicantId = -1
|
||||||
|
|
||||||
|
func resetPlayer() {
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func playOrPause(applicantId: Int, url: String) {
|
||||||
|
if applicantId <= 0 {
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.applicantId != applicantId {
|
||||||
|
stop()
|
||||||
|
setupPlayer(with: url)
|
||||||
|
self.applicantId = applicantId
|
||||||
|
} else {
|
||||||
|
if !isPlaying {
|
||||||
|
player?.play()
|
||||||
|
isPlaying = player?.isPlaying ?? false
|
||||||
|
startTimer()
|
||||||
|
} else {
|
||||||
|
player?.pause()
|
||||||
|
isPlaying = player?.isPlaying ?? false
|
||||||
|
stopTimer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func stop() {
|
||||||
|
stopTimer()
|
||||||
|
isPlaying = false
|
||||||
|
isLoading = false
|
||||||
|
player?.stop()
|
||||||
|
player?.currentTime = 0
|
||||||
|
currentTime = 0.0
|
||||||
|
duration = 0.0
|
||||||
|
applicantId = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupPlayer(with url: String) {
|
||||||
|
guard let url = URL(string: url) else {
|
||||||
|
self.errorMessage = "오류가 발생했습니다. 다시 시도해 주세요."
|
||||||
|
self.isShowPopup = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading = true
|
||||||
|
URLSession.shared.dataTask(with: url) { [unowned self] data, response, error in
|
||||||
|
guard let audioData = data else {
|
||||||
|
self.isLoading = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
self.player = try AVAudioPlayer(data: audioData)
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.player?.volume = 1
|
||||||
|
self.player?.delegate = self
|
||||||
|
self.player?.prepareToPlay()
|
||||||
|
|
||||||
|
self.player?.play()
|
||||||
|
self.duration = self.player?.duration ?? 0.0
|
||||||
|
self.isPlaying = self.player.isPlaying
|
||||||
|
self.isLoading = false
|
||||||
|
self.startTimer()
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.errorMessage = "오류가 발생했습니다. 다시 시도해 주세요."
|
||||||
|
self.isShowPopup = true
|
||||||
|
self.isLoading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func startTimer() {
|
||||||
|
stopTimer() // 기존 타이머 제거
|
||||||
|
timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] _ in
|
||||||
|
self?.updateCurrentTime()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 타이머 정지
|
||||||
|
private func stopTimer() {
|
||||||
|
timer?.invalidate()
|
||||||
|
timer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateCurrentTime() {
|
||||||
|
guard let audioPlayer = player else { return }
|
||||||
|
currentTime = audioPlayer.currentTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AuditionSoundManager: AVAudioPlayerDelegate {
|
||||||
|
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ struct AuditionRoleDetailView: View {
|
||||||
|
|
||||||
@StateObject var viewModel = AuditionRoleDetailViewModel()
|
@StateObject var viewModel = AuditionRoleDetailViewModel()
|
||||||
@StateObject var keyboardHandler = KeyboardHandler()
|
@StateObject var keyboardHandler = KeyboardHandler()
|
||||||
|
@StateObject var soundManager = AuditionSoundManager.shared
|
||||||
|
|
||||||
@State private var isShowApplyMethodView = false
|
@State private var isShowApplyMethodView = false
|
||||||
@State private var isShowSelectAudioView = false
|
@State private var isShowSelectAudioView = false
|
||||||
|
@ -123,10 +124,28 @@ struct AuditionRoleDetailView: View {
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.popup(isPresented: $soundManager.isShowPopup, type: .toast, position: .bottom, autohideIn: 2) {
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
Text(soundManager.errorMessage)
|
||||||
|
.padding(.vertical, 13.3)
|
||||||
|
.frame(width: screenSize().width - 66.7, alignment: .center)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
|
.background(Color.button)
|
||||||
|
.foregroundColor(Color.white)
|
||||||
|
.multilineTextAlignment(.leading)
|
||||||
|
.cornerRadius(20)
|
||||||
|
.padding(.bottom, 66.7)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
viewModel.onFailure = { AppState.shared.back() }
|
viewModel.onFailure = { AppState.shared.back() }
|
||||||
viewModel.auditionRoleId = roleId
|
viewModel.auditionRoleId = roleId
|
||||||
}
|
}
|
||||||
|
.onDisappear {
|
||||||
|
soundManager.resetPlayer()
|
||||||
|
}
|
||||||
|
|
||||||
if let roleDetail = viewModel.auditionRoleDetail {
|
if let roleDetail = viewModel.auditionRoleDetail {
|
||||||
Text(roleDetail.isAlreadyApplicant ? "오디션 재지원" : "오디션 지원")
|
Text(roleDetail.isAlreadyApplicant ? "오디션 재지원" : "오디션 지원")
|
||||||
|
@ -208,6 +227,10 @@ struct AuditionRoleDetailView: View {
|
||||||
viewModel.isShowNotifyVote = false
|
viewModel.isShowNotifyVote = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if soundManager.isLoading {
|
||||||
|
LoadingView()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue