parent
a3beb9c9fe
commit
22ab76d664
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
ZStack {
|
||||||
KFImage(URL(string: imageUrl))
|
KFImage(URL(string: imageUrl))
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.scaledToFit()
|
.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일전",
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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일전",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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일전",
|
||||||
|
|
Loading…
Reference in New Issue