재생목록 플레이어 추가
This commit is contained in:
189
SodaLive/Sources/Content/Player/ContentPlayerView.swift
Normal file
189
SodaLive/Sources/Content/Player/ContentPlayerView.swift
Normal file
@@ -0,0 +1,189 @@
|
||||
//
|
||||
// ContentPlayerView.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 12/16/24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Kingfisher
|
||||
import Sliders
|
||||
|
||||
struct ContentPlayerView: View {
|
||||
|
||||
@StateObject var playerManager = ContentPlayerPlayManager.shared
|
||||
|
||||
@Binding var isShowing: Bool
|
||||
let playlist: [AudioContentPlaylistContent]
|
||||
|
||||
var body: some View {
|
||||
BaseView(isLoading: $playerManager.isLoading) {
|
||||
VStack(spacing: 0) {
|
||||
HStack(spacing: 0) {
|
||||
Spacer()
|
||||
|
||||
Image("ic_bottom_white")
|
||||
.onTapGesture { isShowing = false }
|
||||
}
|
||||
|
||||
Text(playerManager.title)
|
||||
.font(.custom(Font.medium.rawValue, size: 16))
|
||||
.foregroundColor(.grayee)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.top, 16)
|
||||
|
||||
HStack(spacing: 5.3) {
|
||||
KFImage(URL(string: playerManager.creatorProfileUrl))
|
||||
.cancelOnDisappear(true)
|
||||
.downsampling(size: CGSize(width: 26.7, height: 26.7))
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 26.7, height: 26.7)
|
||||
.clipShape(Circle())
|
||||
|
||||
Text(playerManager.nickname)
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(.gray90)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.top, 21)
|
||||
|
||||
Spacer()
|
||||
|
||||
if playerManager.isShowPlaylist {
|
||||
ScrollView(.vertical) {
|
||||
LazyVStack(alignment: .leading, spacing: 0) {
|
||||
ForEach(0..<playerManager.playlist.count, id: \.self) {
|
||||
PlaylistContentItemView(item: playerManager.playlist[$0])
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
KFImage(URL(string: playerManager.coverImageUrl))
|
||||
.cancelOnDisappear(true)
|
||||
.downsampling(size: CGSize(width: 240, height: 240))
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 240, height: 240)
|
||||
.cornerRadius(8)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if playerManager.duration > 0 {
|
||||
ValueSlider(
|
||||
value: $playerManager.currentTime,
|
||||
in: 0...playerManager.duration,
|
||||
step: 1.0,
|
||||
onEditingChanged: { editing in
|
||||
playerManager.isEditing = editing
|
||||
if !editing {
|
||||
playerManager.seek(to: playerManager.currentTime)
|
||||
}
|
||||
}
|
||||
)
|
||||
.valueSliderStyle(
|
||||
HorizontalValueSliderStyle(
|
||||
track: HorizontalValueTrack(
|
||||
view: Rectangle().foregroundColor(Color.button),
|
||||
mask: Rectangle()
|
||||
)
|
||||
.background(Rectangle().foregroundColor(Color.gray97.opacity(0.3)))
|
||||
.frame(height: 5.3),
|
||||
thumbSize: CGSize(width: 10, height: 10),
|
||||
options: .interactiveTrack
|
||||
)
|
||||
)
|
||||
.frame(height: 11)
|
||||
}
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text(secondsToMinutesSeconds(seconds: Int(playerManager.currentTime)))
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.foregroundColor(.graybb)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text(secondsToMinutesSeconds(seconds: Int(playerManager.duration)))
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.foregroundColor(.gray77)
|
||||
}
|
||||
.padding(.top, 5.3)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Image("ic_skip_back")
|
||||
.onTapGesture {
|
||||
playerManager.playPreviousContent()
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(playerManager.isPlaying ? "ic_player_pause" : "ic_player_play")
|
||||
.onTapGesture {
|
||||
playerManager.playOrPause()
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Image("ic_skip_forward")
|
||||
.onTapGesture {
|
||||
playerManager.playNextContent()
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 21)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Spacer()
|
||||
|
||||
Image("ic_playlist")
|
||||
.padding(5)
|
||||
.background(Color.gray33.opacity(playerManager.isShowPlaylist ? 1 : 0))
|
||||
.cornerRadius(playerManager.isShowPlaylist ? 6.7 : 0)
|
||||
.onTapGesture { playerManager.isShowPlaylist.toggle() }
|
||||
}
|
||||
}
|
||||
.padding(20)
|
||||
.onAppear {
|
||||
if !playlist.isEmpty {
|
||||
playerManager.setPlaylist(playlist: playlist)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)"
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ContentPlayerView(
|
||||
isShowing: .constant(true),
|
||||
playlist: [
|
||||
AudioContentPlaylistContent(
|
||||
id: 1,
|
||||
title: "안녕하세요 오늘은 커버곡을 들려드리려고 해요",
|
||||
category: "커버곡",
|
||||
coverUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||
duration: "00:30:20",
|
||||
creatorNickname: "유저1",
|
||||
creatorProfileUrl: "https://test-cf.sodalive.net/profile/default-profile.png"
|
||||
),
|
||||
|
||||
AudioContentPlaylistContent(
|
||||
id: 2,
|
||||
title: "안녕하세요 오늘은 커버곡을 들려드리려고 해요안녕하세요 오늘은 커버곡을 들려드리려고 해요안녕하세요 오늘은 커버곡을 들려드리려고 해요안녕하세요 오늘은 커버곡을 들려드리려고 해요안녕하세요 오늘은 커버곡을 들려드리려고 해요안녕하세요 오늘은 커버곡을 들려드리려고 해요",
|
||||
category: "커버곡",
|
||||
coverUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||
duration: "00:30:20",
|
||||
creatorNickname: "유저1",
|
||||
creatorProfileUrl: "https://test-cf.sodalive.net/profile/default-profile.png"
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user