크리에이터 커뮤니티 게시글 전체리스트

- 오디오 재생기능 추가
This commit is contained in:
Yu Sung 2024-08-07 14:48:12 +09:00
parent a3beb9c9fe
commit 22ab76d664
7 changed files with 181 additions and 5 deletions

View File

@ -57,6 +57,9 @@ struct SodaLiveApp: App {
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
ContentView() ContentView()
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)) { _ in
CreatorCommunityMediaPlayerManager.shared.pauseContent()
}
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in .onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
UIApplication.shared.applicationIconBadgeNumber = 0 UIApplication.shared.applicationIconBadgeNumber = 0

View File

@ -21,6 +21,9 @@ struct CreatorCommunityAllItemView: View {
@State var likeCount = 0 @State var likeCount = 0
@State private var textHeight: CGFloat = .zero @State private var textHeight: CGFloat = .zero
@StateObject var playManager = CreatorCommunityMediaPlayerManager.shared
@StateObject var contentPlayManager = ContentPlayManager.shared
init( init(
item: GetCommunityPostListResponse, item: GetCommunityPostListResponse,
onClickLike: @escaping () -> Void, onClickLike: @escaping () -> Void,
@ -88,10 +91,20 @@ struct CreatorCommunityAllItemView: View {
if item.price <= 0 || item.existOrdered { if item.price <= 0 || item.existOrdered {
if let imageUrl = item.imageUrl { if let imageUrl = item.imageUrl {
KFImage(URL(string: imageUrl)) ZStack {
.resizable() KFImage(URL(string: imageUrl))
.frame(maxWidth: .infinity) .resizable()
.scaledToFit() .frame(maxWidth: .infinity)
.scaledToFit()
if let audioUrl = item.audioUrl {
Image(playManager.isPlaying && playManager.currentPlayingContentId == item.postId ? "btn_audio_content_pause" : "btn_audio_content_play")
.onTapGesture {
contentPlayManager.pauseAudio()
playManager.toggleContent(item: CreatorCommunityContentItem(contentId: item.postId, url: audioUrl))
}
}
}
} }
HStack(spacing: 8) { HStack(spacing: 8) {
@ -155,6 +168,7 @@ struct CreatorCommunityAllItemView_Previews: PreviewProvider {
creatorNickname: "민하나", creatorNickname: "민하나",
creatorProfileUrl: "https://test-cf.sodalive.net/profile/default-profile.png", creatorProfileUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
imageUrl: "https://test-cf.sodalive.net/profile/default-profile.png", imageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
audioUrl: nil,
content: "너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!", content: "너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!",
price: 10, price: 10,
date: "3일전", date: "3일전",

View File

@ -12,6 +12,7 @@ struct CreatorCommunityAllView: View {
let creatorId: Int let creatorId: Int
@StateObject var viewModel = CreatorCommunityAllViewModel() @StateObject var viewModel = CreatorCommunityAllViewModel()
@StateObject var playerManager = CreatorCommunityMediaPlayerManager.shared
var body: some View { var body: some View {
GeometryReader { proxy in GeometryReader { proxy in
@ -135,6 +136,10 @@ struct CreatorCommunityAllView: View {
viewModel.purchaseCommunityPost() viewModel.purchaseCommunityPost()
} }
} }
if playerManager.isLoading {
LoadingView()
}
} }
} }
.popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .top, autohideIn: 2) { .popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .top, autohideIn: 2) {
@ -145,7 +150,24 @@ struct CreatorCommunityAllView: View {
.padding(.vertical, 13.3) .padding(.vertical, 13.3)
.frame(width: screenSize().width - 66.7, alignment: .center) .frame(width: screenSize().width - 66.7, alignment: .center)
.font(.custom(Font.medium.rawValue, size: 12)) .font(.custom(Font.medium.rawValue, size: 12))
.background(Color(hex: "9970ff")) .background(Color.button)
.foregroundColor(Color.white)
.multilineTextAlignment(.center)
.cornerRadius(20)
.padding(.top, 66.7)
Spacer()
}
}
}
.popup(isPresented: $playerManager.isShowPopup, type: .toast, position: .top, autohideIn: 2) {
GeometryReader { geo in
HStack {
Spacer()
Text(playerManager.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) .foregroundColor(Color.white)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.cornerRadius(20) .cornerRadius(20)
@ -158,6 +180,9 @@ struct CreatorCommunityAllView: View {
viewModel.creatorId = creatorId viewModel.creatorId = creatorId
viewModel.getCommunityPostList() viewModel.getCommunityPostList()
} }
.onDisappear {
CreatorCommunityMediaPlayerManager.shared.stopContent()
}
} }
} }

View File

@ -0,0 +1,130 @@
//
// CreatorCommunityMediaPlayerManager.swift
// SodaLive
//
// Created by klaus on 8/7/24.
//
import Foundation
import AVKit
import MediaPlayer
final class CreatorCommunityMediaPlayerManager: NSObject, ObservableObject {
static let shared = CreatorCommunityMediaPlayerManager()
@Published private (set) var currentPlayingContentId: Int = 0
@Published private (set) var isPlaying = false
@Published var isLoading = false
@Published var errorMessage = ""
@Published var isShowPopup = false
private var player: AVAudioPlayer!
private func playContent(item: CreatorCommunityContentItem) {
if item.contentId <= 0 {
return
}
if (currentPlayingContentId == item.contentId && !isPlaying) {
resumeContent()
return
}
stopContent()
currentPlayingContentId = item.contentId
guard let url = URL(string: item.url) else {
showError()
return
}
isLoading = true
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)
DispatchQueue.main.async {
self.player?.volume = 1
self.player?.delegate = self
self.player?.prepareToPlay()
self.player?.play()
self.isPlaying = self.player.isPlaying
}
} catch {
DispatchQueue.main.async {
self.showError()
}
}
DispatchQueue.main.async {
self.isLoading = false
}
}.resume()
}
private func resumeContent() {
if let player = player {
player.play()
isPlaying = player.isPlaying
}
}
private func showError() {
self.errorMessage = "오류가 발생했습니다. 다시 시도해 주세요."
self.isShowPopup = true
}
}
extension CreatorCommunityMediaPlayerManager {
func toggleContent(item: CreatorCommunityContentItem) {
if currentPlayingContentId == item.contentId {
if let player = player, player.isPlaying {
pauseContent()
} else {
resumeContent()
}
} else {
playContent(item: item)
}
}
func pauseContent() {
if let player = player {
player.pause()
isPlaying = player.isPlaying
}
}
func stopContent() {
if let player = player {
player.stop()
player.currentTime = 0
}
isPlaying = false
currentPlayingContentId = 0
}
}
extension CreatorCommunityMediaPlayerManager: AVAudioPlayerDelegate {
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
stopContent()
}
}
struct CreatorCommunityContentItem {
let contentId: Int
let url: String
}

View File

@ -91,6 +91,7 @@ struct CreatorCommunityItemView_Previews: PreviewProvider {
creatorNickname: "민하나", creatorNickname: "민하나",
creatorProfileUrl: "https://test-cf.sodalive.net/profile/default-profile.png", creatorProfileUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
imageUrl: "https://test-cf.sodalive.net/profile/default-profile.png", imageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
audioUrl: nil,
content: "안녕하세요", content: "안녕하세요",
price: 10, price: 10,
date: "3일전", date: "3일전",

View File

@ -11,6 +11,7 @@ struct GetCommunityPostListResponse: Decodable {
let creatorNickname: String let creatorNickname: String
let creatorProfileUrl: String let creatorProfileUrl: String
let imageUrl: String? let imageUrl: String?
let audioUrl: String?
let content: String let content: String
let price: Int let price: Int
let date: String let date: String

View File

@ -37,6 +37,7 @@ struct SectionCommunityPostView_Previews: PreviewProvider {
creatorNickname: "민하나", creatorNickname: "민하나",
creatorProfileUrl: "https://test-cf.sodalive.net/profile/default-profile.png", creatorProfileUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
imageUrl: "https://test-cf.sodalive.net/profile/default-profile.png", imageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
audioUrl: nil,
content: "라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!", content: "라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!",
price: 10, price: 10,
date: "3일전", date: "3일전",
@ -54,6 +55,7 @@ struct SectionCommunityPostView_Previews: PreviewProvider {
creatorNickname: "닉네임2", creatorNickname: "닉네임2",
creatorProfileUrl: "https://test-cf.sodalive.net/profile/default-profile.png", creatorProfileUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
imageUrl: "https://test-cf.sodalive.net/profile/default-profile.png", imageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
audioUrl: nil,
content: "너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!", content: "너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!",
price: 10, price: 10,
date: "3일전", date: "3일전",