yoheimuta / RxMusicPlayer

A reactive library to make it easy for audio playbacks using RxSwift.
MIT License
58 stars 8 forks source link

Audio duplication error #62

Open dinhthiet2702 opened 2 years ago

dinhthiet2702 commented 2 years ago

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.

dinhthiet2702 commented 2 years ago

https://user-images.githubusercontent.com/58399579/178775937-ec7a1275-1549-4533-8b59-c6a366b92456.MP4

dinhthiet2702 commented 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

}
yoheimuta commented 2 years ago

@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.

dinhthiet2702 commented 2 years ago

I fixed it, does the current library support playing songs at specified index? ex: click cell tableview -> Play music at indexPath.row

rastaman111 commented 2 years ago

Yes

dinhthiet2702 commented 2 years ago

Yes

Can you show me that function?

rastaman111 commented 2 years ago

Yes

Can you show me that function?

player?.playIndex = playIndex

dinhthiet2702 commented 2 years ago

Yes

Can you show me that function?

player?.playIndex = playIndex

thanks sir hehehe

rastaman111 commented 2 years ago

πŸ‘ŒπŸΌπŸ‘πŸΌπŸ˜ƒ