Open dinhthiet2702 opened 2 years ago
var songs:[Song] = [] { didSet { if index >= songs.count{ index = 0 } // let metaData = RxMusicPlayerItem(url: <#T##URL#>, meta: RxMusicPlayerItem.Meta(duration: <#T##CMTime?#>, lyrics: <#T##String?#>, title: <#T##String?#>, album: <#T##String?#>, artist: <#T##String?#>, artwork: <#T##UIImage?#>, skipDownloading: <#T##Bool#>)) let items = songs.map({ RxMusicPlayerItem(url: URL(string: $0.filename?.queryURL ?? "")!) }) self.player = RxMusicPlayer(items: Array(items[index ..< songs.count]))
setupPlayer()
}
}
var index = 0 {
didSet{
// setupSong() } }
var player: RxMusicPlayer?
public override func viewDidLoad() {
super.viewDidLoad()
setupUI()
// setupRemoteCommandCenter()
}
func setupPlayer() {
guard let player = player else {
return
}
// 2) Control views
player.rx.canSendCommand(cmd: .play)
.do(onNext: { [weak self] canPlay in
self?.isPlaying = canPlay
})
.drive()
.disposed(by: disposeBag)
player.rx.canSendCommand(cmd: .next)
.drive(btnNext.rx.isEnabled)
.disposed(by: disposeBag)
player.rx.canSendCommand(cmd: .previous)
.drive(btnPre.rx.isEnabled)
.disposed(by: disposeBag)
player.rx.canSendCommand(cmd: .seek(seconds: 0, shouldPlay: false))
.drive(onTime.rx.isUserInteractionEnabled)
.disposed(by: disposeBag)
player.rx.currentItemTitle()
.drive(lbName.rx.text)
.disposed(by: disposeBag)
player.rx.currentItemArtwork()
.drive(onNext: {[weak self] image in
self?.imv.image = image
self?.imv.image = image
})
.disposed(by: disposeBag)
// player.rx.currentItemLyrics() // .distinctUntilChanged() // .do(onNext: { [weak self] _ in // self?.tableView.reloadData() // }) // .drive(lyricsLabel.rx.text) // .disposed(by: disposeBag)
player.rx.currentItemRestDurationDisplay()
.map {
guard let rest = $0 else { return "--:--" }
return "-\(rest)"
}
.drive(lbEnd.rx.text)
.disposed(by: disposeBag)
player.rx.currentItemTimeDisplay()
.drive(lbStart.rx.text)
.disposed(by: disposeBag)
player.rx.currentItemDuration()
.map { Float($0?.seconds ?? 0) }
.do(onNext: { [weak self] in
self?.onTime.maximumValue = $0
})
.drive()
.disposed(by: disposeBag)
let seekValuePass = BehaviorRelay<Bool>(value: true)
player.rx.currentItemTime()
.withLatestFrom(seekValuePass.asDriver()) { ($0, $1) }
.filter { $0.1 }
.map { Float($0.0?.seconds ?? 0) }
.drive(onTime.rx.value)
.disposed(by: disposeBag)
onTime.rx.controlEvent(.touchDown)
.do(onNext: {
seekValuePass.accept(false)
})
.subscribe()
.disposed(by: disposeBag)
onTime.rx.controlEvent(.touchUpInside)
.do(onNext: {
seekValuePass.accept(true)
})
.subscribe()
.disposed(by: disposeBag)
player.rx.currentItemLoadedProgressRate()
.drive(onTime.rx.playableProgress)
.disposed(by: disposeBag)
player.rx.shuffleMode()
.do(onNext: { [weak self] mode in
self?.isShuffled = mode == .songs
})
.drive()
.disposed(by: disposeBag)
player.rx.repeatMode()
.do(onNext: { [weak self] mode in
var title = ""
switch mode {
case .none: title = "Repeat"
case .one: title = "Repeat(All)"
case .all: title = "No Repeat"
@unknown default:
break
}
self?.btnRepeat.setTitle(title, for: .normal)
})
.drive()
.disposed(by: disposeBag)
player.rx.playerIndex()
.do(onNext: { index in
if index == player.queuedItems.count - 1 {
// You can remove the comment-out below to confirm the append().
// player.append(items: items)
}
})
.drive()
.disposed(by: disposeBag)
// 3) Process the user's input
let cmd = Driver.merge(
btnPlay.rx.tap.asDriver().map { [weak self] in
if self?.isPlaying == true {
return RxMusicPlayer.Command.play
}
return RxMusicPlayer.Command.pause
},
btnNext.rx.tap.asDriver().map { RxMusicPlayer.Command.next },
btnPre.rx.tap.asDriver().map { RxMusicPlayer.Command.previous },
onTime.rx.controlEvent(.valueChanged).asDriver()
.map { [weak self] _ in
RxMusicPlayer.Command.seek(seconds: Int(self?.onTime.value ?? 0),
shouldPlay: false)
}
.distinctUntilChanged()
)
.startWith(.prefetch)
.debug()
// You can remove the comment-out below to confirm changing the current index of music items.
// Default is 0.
// player.playIndex = 1
player.run(cmd: cmd)
.do(onNext: { status in
UIApplication.shared.isNetworkActivityIndicatorVisible = status == .loading
})
.flatMap { [weak self] status -> Driver<()> in
guard let weakSelf = self else { return .just(()) }
switch status {
case let RxMusicPlayer.Status.failed(err: err):
print(err)
return Wireframe.promptOKAlertFor(src: weakSelf,
title: "Error",
message: err.localizedDescription)
case let RxMusicPlayer.Status.critical(err: err):
print(err)
return Wireframe.promptOKAlertFor(src: weakSelf,
title: "Critical Error",
message: err.localizedDescription)
default:
print(status)
}
return .just(())
}
.drive()
.disposed(by: disposeBag)
btnShuffe.rx.tap.asDriver()
.drive(onNext: {
switch player.shuffleMode {
case .off: player.shuffleMode = .songs
case .songs: player.shuffleMode = .off
}
})
.disposed(by: disposeBag)
btnRepeat.rx.tap.asDriver()
.drive(onNext: {
switch player.repeatMode {
case .none: player.repeatMode = .one
case .one: player.repeatMode = .all
case .all: player.repeatMode = .none
}
})
.disposed(by: disposeBag)
// rateButton.rx.tap.asDriver() // .flatMapLatest { [weak self] -> Driver<()> in // guard let weakSelf = self else { return .just(()) } // // return Wireframe.promptSimpleActionSheetFor( // src: weakSelf, // cancelAction: "Close", // actions: PlaybackRateAction.allCases.map { // ($0.rawValue, player.desiredPlaybackRate == $0.toFloat) // }) // .do(onNext: { [weak self] action in // if let rate = PlaybackRateAction(rawValue: action)?.toFloat { // player.desiredPlaybackRate = rate // self?.rateButton.setTitle(action, for: .normal) // } // }) // .map { in } // } // .drive() // .disposed(by: disposeBag)
// appendButton.rx.tap.asDriver() // .do(onNext: { // let newItems = Array(items[4 ..< 6]) // player.append(items: newItems) // }) // .drive(onNext: { [weak self] _ in // self?.appendButton.isEnabled = false // }) // .disposed(by: disposeBag)
// changeButton.rx.tap.asObservable() // .flatMapLatest { [weak self] -> Driver<()> in // guard let weakSelf = self else { return .just(()) } // // return Wireframe.promptSimpleActionSheetFor( // src: weakSelf, // cancelAction: "Close", // actions: items.map { // ($0.url.lastPathComponent, player.queuedItems.contains($0)) // }) // .asObservable() // .do(onNext: { action in // if let idx = player.queuedItems.map({ $0.url.lastPathComponent }).firstIndex(of: action) { // try player.remove(at: idx) // } else if let idx = items.map({ $0.url.lastPathComponent }).firstIndex(of: action) { // for i in (0 ... idx).reversed() { // if let prev = player.queuedItems.firstIndex(of: items[i]) { // player.insert(items[idx], at: prev + 1) // break // } // if i == 0 { // player.insert(items[idx], at: 0) // } // } // } // // self?.appendButton.isEnabled = !(player.queuedItems.contains(items[4]) // || player.queuedItems.contains(items[5])) // }) // .asDriver(onErrorJustReturn: "") // .map { in } // } // .asDriver(onErrorJustReturn: ()) // .drive() // .disposed(by: disposeBag) }
public override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
imv.radius(radius: 10)
btnShuffe.centerVertically(padding: 8)
btnLike.centerVertically(padding: 8)
btnRepeat.centerVertically(padding: 8)
}
func setupUI() {
setupContraint()
vStack.setCustomSpacing(100, after: vStack.arrangedSubviews[0])
customBtnPre.setImage(Images.PREMUSIC_POP.imageOriginal, for: .normal)
customBtnPause.setImage(Images.PAUSE_POP.imageOriginal, for: .normal)
pauseBtn = UIBarButtonItem(customView: customBtnPause)
customBtnNext.setImage(Images.NEXT_POP.imageOriginal, for: .normal)
nextBtn = UIBarButtonItem(customView: customBtnNext)
popupItem.barButtonItems = [preBtn, pauseBtn, nextBtn]
}
func setupContraint() {
view.addSubview(imvBg)
view.addSubview(vStack)
[stackView1, stackView2, stackView3, stackView4].forEach {vStack.addArrangedSubview($0)}
NSLayoutConstraint.activate([
imvBg.topAnchor.constraint(equalTo: view.topAnchor, constant: 0),
imvBg.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0),
imvBg.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0),
imvBg.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0),
vStack.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 70),
vStack.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 15),
vStack.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -15)
])
[imv, lbName].forEach {stackView1.addArrangedSubview($0)}
[onTime, stackView5].forEach {stackView2.addArrangedSubview($0)}
[lbStart, lbEnd].forEach {stackView5.addArrangedSubview($0)}
[btnPre, btnPlay, btnNext].forEach {stackView3.addArrangedSubview($0)}
[btnShuffe, btnRepeat, btnLike].forEach {stackView4.addArrangedSubview($0)}
onTime.widthAnchor.constraint(equalTo: vStack.widthAnchor, multiplier: 1).isActive = true
}
@dinhthiet2702 Thank you for reaching out. Can you reproduce it when you use our example projects hosted under this repository?
Because I couldn't encounter the issues while playing example projects. And see also https://github.com/yoheimuta/RxMusicPlayer#bug-report.
I fixed it, does the current library support playing songs at specified index? ex: click cell tableview -> Play music at indexPath.row
Yes
Yes
Can you show me that function?
Yes
Can you show me that function?
player?.playIndex = playIndex
Yes
Can you show me that function?
player?.playIndex = playIndex
thanks sir hehehe
ππΌππΌπ
I'm using your library but still there is a bug where when pressing next repeatedly or once, sometimes the same song plays 2 times in parallel.