Skip to content

Commit

Permalink
Feature/random music (#13)
Browse files Browse the repository at this point in the history
* feat: Add RandomMusicQuiz logic

- Reactor 기능 추가

 #8

* feat: Add RandomMusicQuizView

- view 추가
- 장르 선택 영역 추가 (변경 가능성 있음)

 #8

* feat: RandomMusicQuiz view, binding logic

- view 수정
- state 와 action 바인딩
- Music model float 로 변경

- 음악 재생/중지
- 업데이트, 셔플
- 정답 보기

 # 8

* feat: Add RandomMusic to home

- 홈에서 RandomMusicQuiz 로 이동 가능하도록 추가
- 게임 모드를 enum 으로 변경

* style: Code reformatting

- 들여쓰기 4개로 수정

* Revert "style: Code reformatting"

This reverts commit d08979f.

* refactor: modify style #13

- 재사용 않는 클래스 final 로 변경
- 모델 파일 분리
- 네이밍 수정

* fix: Fix infinite loading

- 무한로딩되던 문제 해결
- ytPlayer 라이브러리에서 delegate 호출이 정상적으로 이루어지지 않아
  발생
- ytPlayer 로의 요청중일 때 막는 방식으로 해결

* fix: Fix playing timer

비디오 재생과정은 아래와 같다.

1. 비디오 로드 // pending
2. 비디오 준비 // ready
3. 비디오 재생 명령 // .startVideo()
4-1. 비디오 버퍼링 // buffering
4-2. 비디오 재생

비디오 재생을 수행 시 버퍼링 과정이 포함되는데, 버퍼링 -> 재생까지의
시각이 매번 달라 YTPlayer 의 state 를 확인하여 재생되는 시점을 확인하고
재생되도록 하였음.

- 다른 state (isReady) 와 같은 state 는 PlayerState 로 관리하도록 수정
- 재생 / 정지 동작을 명령할 state 네이밍 수정

fix #8

* fix: Fix playing time error

시간초 재생 후 시작을 눌렀을 때 시간초 재생이 이루어지던 오류 수정

Co-authored-by: JaeWook Lee <[email protected]>
  • Loading branch information
elppaaa and 2jae6 authored Apr 27, 2022
1 parent 7521258 commit 7e8dd1f
Show file tree
Hide file tree
Showing 11 changed files with 623 additions and 60 deletions.
13 changes: 13 additions & 0 deletions LiarGame/Sources/Model/GameMode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// GameMode.swift
// LiarGame
//
// Created by JK on 2022/04/26.
//

import Foundation

enum GameMode {
case liarGame
case randomMusicQuiz
}
9 changes: 5 additions & 4 deletions LiarGame/Sources/Model/Music.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@

import Foundation

struct Music: Codable {
struct Music: Codable, Equatable {
let title: String
let artist: String
/// youtube video ID
let id: String
let startedAt: String
let startedAt: Float

/** Music 생성자
Expand All @@ -27,12 +27,13 @@ struct Music: Codable {
길이가 맞지 않을 경우 `return nil`
*/
init?(from array: [String]) {
guard array.count == 4 else { return nil }
guard array.count == 4,
let startedAt = Float(array[3]) else { return nil }

self.title = array[0]
self.artist = array[1]
self.id = array[2]
self.startedAt = array[3]
self.startedAt = startedAt
}
}

7 changes: 3 additions & 4 deletions LiarGame/Sources/Reactor/HomeReactor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,17 @@ import RxCocoa

final class HomeReactor: Reactor{
enum Action{
case updateMode(String?)
case updateMode(GameMode)
}
enum Mutation{
case setMode(String?)
case setMode(GameMode)
}
struct State{
var mode: String?
var mode: GameMode?
}

let initialState = State()


func mutate(action: Action) -> Observable<Mutation> {
switch action {
case let .updateMode(mode):
Expand Down
154 changes: 150 additions & 4 deletions LiarGame/Sources/Reactor/RandomMusicQuizReactor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,162 @@ import Foundation
import ReactorKit

final class RandomMusicQuizReactor: Reactor {
init(repository: RandomMusicRepository) {
self.repository = repository
}

private var disposeBag = DisposeBag()

var scheduler = SerialDispatchQueueScheduler(internalSerialQueueName: "random.music.quiz")
var initialState = State()
private let repository: RandomMusicRepository
private var playerState: PlayerState = .unknwon
private var second: PlaySecond?

enum PlaySecond: Int {
case three = 3
case five = 5
case ten = 10
}

enum PlayerState {
/// 비디오 로드 후 대기 중
case pending
/// 비디오 로드 완료
case ready
/// 비디오 재생 시 버퍼링
case buffering
/// 비디오 재생 중
case playing
/// 재생정지(stopVideo) 호출 시 cued
case cued
case unknwon
}

enum Action {
case updateMusicList
case playMusicButtonTapped(second: PlaySecond)
case didPlayToggleButtonTapped
case didAnswerButtonTapped
case shuffle
case playerState(PlayerState)
case needCurrentVersion
}

enum Mutation {
case updatePlayStopState(Bool)
case updateCurrentVersion(String)
case updateCurrentMusic(Music?)
case updateAnswer((String, String)?)
case updateLoading(Bool)
case ignore
}

struct State {
var isPlaying: Bool = false
var isLoading: Bool = false
var currentVersion: String = ""
var answer: (title: String, artist: String)?
var currentMusic: Music?
}

enum Action { }
enum Mutation { }
struct State { }
func mutate(action: Action) -> Observable<Mutation> {

switch action {
case .updateMusicList:
guard playerState != .pending else { return .empty() }
playerState = .pending
return .concat([
.just(.updateLoading(true)),
.just(.updatePlayStopState(false)),
.just(.updateAnswer(nil)),
repository.getNewestVersion()
.asObservable()
.map { _ in Mutation.ignore },
.just(.updateCurrentMusic(shuffleMusic())),
.just(.updateCurrentVersion(repository.currentVersion))
])
.timeout(.seconds(10), other: Observable.just(Mutation.updateLoading(false)), scheduler: scheduler)

case let .playMusicButtonTapped(second):
self.second = second
return .concat([
.just(.updatePlayStopState(true)),
])

case .didPlayToggleButtonTapped:
return .just(.updatePlayStopState(!currentState.isPlaying))

case .didAnswerButtonTapped:
return .just(.updateAnswer(currentAnswer()))

case .shuffle:
guard playerState != .pending else { return .empty() }
playerState = .pending
return .concat(
.just(.updateLoading(true)),
.just(.updatePlayStopState(false)),
.just(.updateAnswer(nil)),
.just(.updateCurrentMusic(shuffleMusic()))
)

case .needCurrentVersion:
return .just(.updateCurrentVersion(repository.currentVersion))

case let .playerState(state):
return playerStateHandler(state)
}
}

func reduce(state: State, mutation: Mutation) -> State {
var state = state
switch mutation {
case let .updatePlayStopState(boolean):
state.isPlaying = boolean
case let .updateCurrentVersion(version):
state.currentVersion = version
case let .updateCurrentMusic(music):
state.currentMusic = music
case let .updateAnswer(info):
state.answer = info
case let .updateLoading(boolean):
state.isLoading = boolean
case .ignore: break
}
return state
}

private func currentAnswer() -> (title: String, artist: String)? {
if let currentMusic = currentState.currentMusic {
return (title: currentMusic.title, artist: currentMusic.artist)
} else {
return nil
}
}

private func shuffleMusic() -> Music? {
guard repository.musicList.count > 0 else { return nil }
let size = repository.musicList.count
let randomNumber: Int = Int(arc4random()) % size

return repository.musicList[randomNumber]
}

// `YTPlayerView.playVideo()` 호출 시점과 실제 재생 시점이 다름
// `YTPlayerView` 의 state 를 확인해서 재생 타이머를 수행
private func playerStateHandler(_ state: PlayerState) -> Observable<Mutation> {
self.playerState = state
guard playerState != .pending else { return .empty() }

switch playerState {
case .playing:
return .just(.updatePlayStopState(false))
case .ready:
return .just(.updateLoading(false))
case .cued:
self.second = nil
fallthrough
default:
return .empty()
}
}
}
10 changes: 4 additions & 6 deletions LiarGame/Sources/Repository/RandomMusicRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ final class RandomMusicRepository: RandomMusicRepositoryType {
Self._currentVersion
}

@UserDefault(key: "RandomMusicVersion", defaultValue: "")
@UserDefault(key: "RandomMusicVersion", defaultValue: "unknwon")
private static var _currentVersion: String

/** 저장되어있는 음악 목록을 가져옵니다.
Expand Down Expand Up @@ -61,7 +61,7 @@ final class RandomMusicRepository: RandomMusicRepositoryType {
func getNewestVersion() -> Single<[Music]> {
_newsetVersion
.flatMap { _version -> Single<[Music]> in
let arr = _version.components(separatedBy: ",")
let arr = _version.trimmingCharacters(in: .whitespacesAndNewlines).components(separatedBy: ",")
let version = arr[0]
let length = arr[1]
guard version != self.currentVersion else { return .just(try self.readMuicList()) }
Expand Down Expand Up @@ -141,17 +141,15 @@ final class RandomMusicRepository: RandomMusicRepositoryType {
}

/// 음악 데이터 읽어오기
// TODO: - private 으로 변경///
func readMuicList() throws -> [Music] {
private func readMuicList() throws -> [Music] {
let path = try musicDataPath()
let data = try Data(contentsOf: path)

return try JSONDecoder().decode([Music].self, from: data)
}

/// 음악 데이터를 저장하는 경로
// TODO: - private 으로 변경///
func musicDataPath() throws -> URL {
private func musicDataPath() throws -> URL {
guard let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
throw RandomMusicRepositoryError.fileAccessFailed
}
Expand Down
32 changes: 32 additions & 0 deletions LiarGame/Sources/Utils/Flex+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// Flex+Extensions.swift
// LiarGame
//
// Created by JK on 2022/04/26.
//

import Foundation
import CoreGraphics
import FlexLayout

extension Flex {
@discardableResult
func horizontallySpacing(_ value: CGFloat?) -> Flex {
guard let view = view, view.subviews.count > 1 else { return self }
for (idx, subview) in view.subviews.enumerated() {
if idx == 0 { continue }
subview.flex.marginLeft(value ?? 0)
}
return self
}

@discardableResult
func verticallySpacing(_ value: CGFloat?) -> Flex {
guard let view = view, view.subviews.count > 1 else { return self }
for (idx, subview) in view.subviews.enumerated() {
if idx == 0 { continue }
subview.flex.marginTop(value ?? 0)
}
return self
}
}
21 changes: 21 additions & 0 deletions LiarGame/Sources/Utils/UIColor+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// UIColor+Extensions.swift
// LiarGame
//
// Created by JK on 2022/04/26.
//

import UIKit

extension UIColor {
convenience init(hexString : String) {
if let rgbValue = UInt(hexString, radix: 16) {
let red = CGFloat((rgbValue >> 16) & 0xff) / 255
let green = CGFloat((rgbValue >> 8) & 0xff) / 255
let blue = CGFloat((rgbValue ) & 0xff) / 255
self.init(red: red, green: green, blue: blue, alpha: 1.0)
} else {
self.init(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
}
}
}
Loading

0 comments on commit 7e8dd1f

Please sign in to comment.