Code Monkey home page Code Monkey logo

rxmusicplayer's Introduction

RxMusicPlayer

Build Status Carthage compatible Version License

RxMusicPlayer is a wrapper of avplayer backed by RxSwift to make it easy for audio playbacks.

Features

  • Following the Audio Guidelines for User-Controlled Playback and Recording Apps.
  • Support for streaming both remote and local audio files.
  • Functions to play, pause, stop, play next, play previous, skip forward/backward, prefetch metadata, repeat mode(repeat, repeat all), shuffle mode desired playback rate, seek to a certain second, and append/insert/remove an item into the playlist.
  • Loading metadata, including title, album, artist, artwork, duration, and lyrics.
  • Background mode integration with MPNowPlayingInfoCenter.
  • Remote command control integration with MPRemoteCommandCenter.
  • Interruption handling with AVAudioSession.interruptionNotification.
  • Route change handling with AVAudioSession.routeChangeNotification.
  • Including a fully working example project, one built on UIKit and the other built on SwiftUI.

Runtime Requirements

  • iOS 10.0 or later

Installation

Swift Package Manager

With 2.0.1 and above.

Carthage

github "yoheimuta/RxMusicPlayer"

CocoaPods

pod "RxMusicPlayer"

Usage

For details, refer to the ExampleSwiftUI project or Example project. Plus, see also Users section below.

Example

example

You can implement your audio player with the custom frontend without any delegates, like below.

Based on SwiftUI

import SwiftUI
import Combine
import RxMusicPlayer
import RxSwift
import RxCocoa

final class PlayerModel: ObservableObject {
    private let disposeBag = DisposeBag()
    private let player: RxMusicPlayer
    private let commandRelay = PublishRelay<RxMusicPlayer.Command>()

    @Published var canPlay = true
    @Published var canPlayNext = true
    @Published var canPlayPrevious = true
    @Published var canSkipForward = true
    @Published var canSkipBackward = true
    @Published var title = "Not Playing"
    @Published var artwork: UIImage?
    @Published var restDuration = "--:--"
    @Published var duration = "--:--"
    @Published var shuffleMode = RxMusicPlayer.ShuffleMode.off
    @Published var repeatMode = RxMusicPlayer.RepeatMode.none
    @Published var remoteControl = RxMusicPlayer.RemoteControl.moveTrack

    @Published var sliderValue = Float(0)
    @Published var sliderMaximumValue = Float(0)
    @Published var sliderIsUserInteractionEnabled = false
    @Published var sliderPlayableProgress = Float(0)

    private var cancelBag = Set<AnyCancellable>()
    var sliderValueChanged = PassthroughSubject<Float, Never>()

    init() {
        // 1) Create a player
        let items = [
            URL(string: "https://storage.googleapis.com/great-dev/oss/musicplayer/tagmp3_1473200_1.mp3")!,
            URL(string: "https://storage.googleapis.com/great-dev/oss/musicplayer/tagmp3_2160166.mp3")!,
            URL(string: "https://storage.googleapis.com/great-dev/oss/musicplayer/tagmp3_4690995.mp3")!,
            Bundle.main.url(forResource: "tagmp3_9179181", withExtension: "mp3")!
        ]
        .map({ RxMusicPlayerItem(url: $0) })
        player = RxMusicPlayer(items: items)!

        // 2) Control views
        player.rx.canSendCommand(cmd: .play)
            .do(onNext: { [weak self] canPlay in
                self?.canPlay = canPlay
            })
            .drive()
            .disposed(by: disposeBag)

        player.rx.canSendCommand(cmd: .next)
            .do(onNext: { [weak self] canPlayNext in
                self?.canPlayNext = canPlayNext
            })
            .drive()
            .disposed(by: disposeBag)

        player.rx.canSendCommand(cmd: .previous)
            .do(onNext: { [weak self] canPlayPrevious in
                self?.canPlayPrevious = canPlayPrevious
            })
            .drive()
            .disposed(by: disposeBag)

        player.rx.canSendCommand(cmd: .seek(seconds: 0, shouldPlay: false))
            .do(onNext: { [weak self] canSeek in
                self?.sliderIsUserInteractionEnabled = canSeek
            })
            .drive()
            .disposed(by: disposeBag)

        player.rx.canSendCommand(cmd: .skip(seconds: 15))
            .do(onNext: { [weak self] canSkip in
                self?.canSkipForward = canSkip
            })
            .drive()
            .disposed(by: disposeBag)

        player.rx.canSendCommand(cmd: .skip(seconds: -15))
            .do(onNext: { [weak self] canSkip in
                self?.canSkipBackward = canSkip
            })
            .drive()
            .disposed(by: disposeBag)

        player.rx.currentItemDuration()
            .do(onNext: { [weak self] in
                self?.sliderMaximumValue = Float($0?.seconds ?? 0)
            })
            .drive()
            .disposed(by: disposeBag)

        player.rx.currentItemTime()
            .do(onNext: { [weak self] time in
                self?.sliderValue = Float(time?.seconds ?? 0)
            })
            .drive()
            .disposed(by: disposeBag)

        player.rx.currentItemLoadedProgressRate()
            .do(onNext: { [weak self] rate in
                self?.sliderPlayableProgress = rate ?? 0
            })
            .drive()
            .disposed(by: disposeBag)

        player.rx.currentItemTitle()
            .do(onNext: { [weak self] title in
                self?.title = title ?? ""
            })
            .drive()
            .disposed(by: disposeBag)

        player.rx.currentItemArtwork()
            .do(onNext: { [weak self] artwork in
                self?.artwork = artwork
            })
            .drive()
            .disposed(by: disposeBag)

        player.rx.currentItemRestDurationDisplay()
            .do(onNext: { [weak self] duration in
                self?.restDuration = duration ?? "--:--"
            })
            .drive()
            .disposed(by: disposeBag)

        player.rx.currentItemTimeDisplay()
            .do(onNext: { [weak self] duration in
                if duration == "00:00" {
                    self?.duration = "0:00"
                    return
                }
                self?.duration = duration ?? "--:--"
            })
            .drive()
            .disposed(by: disposeBag)

        player.rx.shuffleMode()
            .do(onNext: { [weak self] mode in
                self?.shuffleMode = mode
            })
            .drive()
            .disposed(by: disposeBag)

        player.rx.repeatMode()
            .do(onNext: { [weak self] mode in
                self?.repeatMode = mode
            })
            .drive()
            .disposed(by: disposeBag)

        player.rx.remoteControl()
            .do(onNext: { [weak self] control in
                self?.remoteControl = control
            })
            .drive()
            .disposed(by: disposeBag)

        // 3) Process the user's input
        player.run(cmd: commandRelay.asDriver(onErrorDriveWith: .empty()))
            .flatMap { status -> Driver<()> in
                switch status {
                case let RxMusicPlayer.Status.failed(err: err):
                    print(err)
                case let RxMusicPlayer.Status.critical(err: err):
                    print(err)
                default:
                    print(status)
                }
                return .just(())
            }
            .drive()
            .disposed(by: disposeBag)

        commandRelay.accept(.prefetch)

        sliderValueChanged
            .removeDuplicates()
            .sink { [weak self] value in
                self?.seek(value: value)
            }
            .store(in: &cancelBag)
    }

    func seek(value: Float?) {
        commandRelay.accept(.seek(seconds: Int(value ?? 0), shouldPlay: false))
    }

    func skip(second: Int) {
        commandRelay.accept(.skip(seconds: second))
    }

    func shuffle() {
        switch player.shuffleMode {
        case .off: player.shuffleMode = .songs
        case .songs: player.shuffleMode = .off
        }
    }

    func play() {
        commandRelay.accept(.play)
    }

    func pause() {
        commandRelay.accept(.pause)
    }

    func playNext() {
        commandRelay.accept(.next)
    }

    func playPrevious() {
        commandRelay.accept(.previous)
    }

    func doRepeat() {
        switch player.repeatMode {
        case .none: player.repeatMode = .one
        case .one: player.repeatMode = .all
        case .all: player.repeatMode = .none
        }
    }

    func toggleRemoteControl() {
        switch remoteControl {
        case .moveTrack:
            player.remoteControl = .skip(second: 15)
        case .skip:
            player.remoteControl = .moveTrack
        }
    }
}

struct PlayerView: View {
    @StateObject private var model = PlayerModel()

    var body: some View {
        ScrollView {
            VStack {
                Spacer()
                    .frame(width: 1, height: 49)

                if let artwork = model.artwork {
                    Image(uiImage: artwork)
                        .resizable()
                        .scaledToFit()
                        .frame(height: 276)
                } else {
                    Spacer()
                        .frame(width: 1, height: 276)
                }

                ProgressSliderView(value: $model.sliderValue,
                                   maximumValue: $model.sliderMaximumValue,
                                   isUserInteractionEnabled: $model.sliderIsUserInteractionEnabled,
                                   playableProgress: $model.sliderPlayableProgress) {
                    model.sliderValueChanged.send($0)
                }
                    .padding(.horizontal)

                HStack {
                    Text(model.duration)
                    Spacer()
                    Text(model.restDuration)
                }
                .padding(.horizontal)

                Spacer()
                    .frame(width: 1, height: 17)

                Text(model.title)

                Spacer()
                    .frame(width: 1, height: 19)

                HStack(spacing: 20.0) {
                    Button(action: {
                        model.shuffle()
                    }) {
                        Text(model.shuffleMode == .off ? "Shuffle" : "No Shuffle")
                    }

                    Button(action: {
                        model.playPrevious()
                    }) {
                        Text("Previous")
                    }
                    .disabled(!model.canPlayPrevious)

                    Button(action: {
                        model.canPlay ? model.play() : model.pause()
                    }) {
                        Text(model.canPlay ? "Play" : "Pause")
                    }

                    Button(action: {
                        model.playNext()
                    }) {
                        Text("Next")
                    }
                    .disabled(!model.canPlayNext)

                    Button(action: {
                        model.doRepeat()
                    }) {
                        Text({
                            switch model.repeatMode {
                            case .none: return "Repeat"
                            case .one: return "Repeat(All)"
                            case .all: return "No Repeat"
                            }
                        }() as String)
                    }
                }

                Group {
                    Spacer()
                        .frame(width: 1, height: 17)

                    HStack(spacing: 20.0) {
                        Button(action: {
                            model.skip(second: -15)
                        }) {
                            Text("SkipBackward")
                        }
                        .disabled(!model.canSkipBackward)

                        Button(action: {
                            model.skip(second: 15)
                        }) {
                            Text("SkipForward")
                        }
                        .disabled(!model.canSkipForward)
                    }
                }

                Group {
                    Spacer()
                        .frame(width: 1, height: 17)

                    HStack(spacing: 20.0) {
                        Button(action: {
                            model.toggleRemoteControl()
                        }) {
                            let control = model.remoteControl == .moveTrack ? "moveTrack" : "skip"
                            Text("RemoteControl: \(control)")
                        }
                    }
                }
            }
        }
    }
}

struct PlayerView_Previews: PreviewProvider {
    static var previews: some View {
        PlayerView()
    }
}

Based on UIKit

import RxCocoa
import RxMusicPlayer
import RxSwift
import UIKit

class TableViewController: UITableViewController {

    @IBOutlet private var playButton: UIButton!
    @IBOutlet private var nextButton: UIButton!
    @IBOutlet private var prevButton: UIButton!
    @IBOutlet private var titleLabel: UILabel!
    @IBOutlet private var artImageView: UIImageView!
    @IBOutlet private var lyricsLabel: UILabel!
    @IBOutlet private var seekBar: ProgressSlider!
    @IBOutlet private var seekDurationLabel: UILabel!
    @IBOutlet private var durationLabel: UILabel!
    @IBOutlet private var shuffleButton: UIButton!
    @IBOutlet private var repeatButton: UIButton!
    @IBOutlet private var rateButton: UIButton!
    @IBOutlet private var appendButton: UIButton!
    @IBOutlet private var changeButton: UIButton!

    private let disposeBag = DisposeBag()

    // swiftlint:disable cyclomatic_complexity
    override func viewDidLoad() {
        super.viewDidLoad()

        // 1) Create a player
        let items = [
            "https://storage.googleapis.com/great-dev/oss/musicplayer/tagmp3_1473200_1.mp3",
            "https://storage.googleapis.com/great-dev/oss/musicplayer/tagmp3_2160166.mp3",
            "https://storage.googleapis.com/great-dev/oss/musicplayer/tagmp3_4690995.mp3",
            "https://storage.googleapis.com/great-dev/oss/musicplayer/tagmp3_9179181.mp3",
            "https://storage.googleapis.com/great-dev/oss/musicplayer/bensound-extremeaction.mp3",
            "https://storage.googleapis.com/great-dev/oss/musicplayer/bensound-littleplanet.mp3",
        ]
        .map({ RxMusicPlayerItem(url: URL(string: $0)!) })
        let player = RxMusicPlayer(items: Array(items[0 ..< 4]))!

        // 2) Control views
        player.rx.canSendCommand(cmd: .play)
            .do(onNext: { [weak self] canPlay in
                self?.playButton.setTitle(canPlay ? "Play" : "Pause", for: .normal)
            })
            .drive()
            .disposed(by: disposeBag)

        player.rx.canSendCommand(cmd: .next)
            .drive(nextButton.rx.isEnabled)
            .disposed(by: disposeBag)

        player.rx.canSendCommand(cmd: .previous)
            .drive(prevButton.rx.isEnabled)
            .disposed(by: disposeBag)

        player.rx.canSendCommand(cmd: .seek(seconds: 0, shouldPlay: false))
            .drive(seekBar.rx.isUserInteractionEnabled)
            .disposed(by: disposeBag)

        player.rx.currentItemTitle()
            .drive(titleLabel.rx.text)
            .disposed(by: disposeBag)

        player.rx.currentItemArtwork()
            .drive(artImageView.rx.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(durationLabel.rx.text)
            .disposed(by: disposeBag)

        player.rx.currentItemTimeDisplay()
            .drive(seekDurationLabel.rx.text)
            .disposed(by: disposeBag)

        player.rx.currentItemDuration()
            .map { Float($0?.seconds ?? 0) }
            .do(onNext: { [weak self] in
                self?.seekBar.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(seekBar.rx.value)
            .disposed(by: disposeBag)
        seekBar.rx.controlEvent(.touchDown)
            .do(onNext: {
                seekValuePass.accept(false)
            })
            .subscribe()
            .disposed(by: disposeBag)
        seekBar.rx.controlEvent(.touchUpInside)
            .do(onNext: {
                seekValuePass.accept(true)
            })
            .subscribe()
            .disposed(by: disposeBag)

        player.rx.currentItemLoadedProgressRate()
            .drive(seekBar.rx.playableProgress)
            .disposed(by: disposeBag)

        player.rx.shuffleMode()
            .do(onNext: { [weak self] mode in
                self?.shuffleButton.setTitle(mode == .off ? "Shuffle" : "No Shuffle", for: .normal)
            })
            .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"
                }
                self?.repeatButton.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(
            playButton.rx.tap.asDriver().map { [weak self] in
                if self?.playButton.currentTitle == "Play" {
                    return RxMusicPlayer.Command.play
                }
                return RxMusicPlayer.Command.pause
            },
            nextButton.rx.tap.asDriver().map { RxMusicPlayer.Command.next },
            prevButton.rx.tap.asDriver().map { RxMusicPlayer.Command.previous },
            seekBar.rx.controlEvent(.valueChanged).asDriver()
                .map { [weak self] _ in
                    RxMusicPlayer.Command.seek(seconds: Int(self?.seekBar.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)

        shuffleButton.rx.tap.asDriver()
            .drive(onNext: {
                switch player.shuffleMode {
                case .off: player.shuffleMode = .songs
                case .songs: player.shuffleMode = .off
                }
            })
            .disposed(by: disposeBag)

        repeatButton.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)
    }
}

Users

Contributing

  • Fork it
  • Run make bootstrap
  • Create your feature branch: git checkout -b your-new-feature
  • Commit changes: git commit -m 'Add your feature'
  • Push to the branch: git push origin your-new-feature
  • Submit a pull request

Release

  • Create a new release on GitHub
  • Publish a new podspec on Cocoapods
    • bundle exec pod trunk push RxMusicPlayer.podspec

Bug Report

While any bug reports are helpful, it's sometimes unable to pinpoint the cause without a reproducible project.

In particular, since RxMusicPlayer depends on RxSwift that is prone to your application program mistakes, it's more essential to decouple the problem.

Therefore, I highly recommend that you submit an issue with that project.

You can create it like the following steps.

  • Fork it
  • Create your feature branch: git checkout -b your-bug-name
  • Add some changes under the Example directory to reproduce the bug
  • Commit changes: git commit -m 'Add a reproducible feature'
  • Push to the branch: git push origin your-bug-name
  • (Optional) Submit a pull request
  • Share it in your issue

The code should not be intertwined but concise, straightforward, and naive.

NOTE: If you can't prepare any reproducible code, you have to elaborate the detail precisely and clearly so that I can reproduce the problem.

License

The MIT License (MIT)

Acknowledgement

Thank you to the following projects and creators.

rxmusicplayer's People

Contributors

yoheimuta avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

rxmusicplayer's Issues

Error skip

Hey! I want to rewind the player 15 seconds forward and backward on the player screen, how can I do this? when I take real seconds + 15 and do seek it works, but when the audio ends and should switch to the next track it gives an error

how to play local file ?

my code based on https://github.com/yoheimuta/RxMusicPlayer/tree/master/ExampleSwiftUI

i add 3 mp3 files to my project

then i change the items
in herehttps://github.com/yoheimuta/RxMusicPlayer/blob/master/ExampleSwiftUI/PlayerView.swift#L41

here's my code:

image

but, it can't play mp3 file

can you help me?

thank you very much

Don't updated speed locked screen

Hello!
After scrolling, I’ll see progress at any time, NowPlayingInfoCenter is not updated on the locked screen
only if you click stop and again the strat, then it will be updated and it is also possible to turn on the audio playback speed at 2x NowPlayingInfoCenter is also not updated, the strip moves like usual 1x speed.

link to an example video: https://yadi.sk/i/znHUhyeHrdKekg

var rate: Float = 1.0
override func viewDidLoad() {
super.viewDidLoad()

     rateSeekSpeed(self.rate)
}
func rateSeekSpeed(_ rate: Float) {
    
    musicPlayer.player?.rx.playerIndex()
    .do(onNext: { index in
        self.musicPlayer.player?.player?.rate = rate
    })
    .drive()
    .disposed(by: musicPlayer.disposeBag)
    
}

@IBAction func rateSpeedButton(_ sender: UIButton) {
    let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
    alert.addAction(UIAlertAction(title: "2x", style: .default, handler: { (alert) in
        self.rate = 2
        self.rateSeekSpeed(self.rate)
    }))
    alert.addAction(UIAlertAction(title: "1.5x", style: .default, handler: { (alert) in
        self.rate = 1.5
        self.rateSeekSpeed(self.rate)
    }))
    alert.addAction(UIAlertAction(title: "1.25x", style: .default, handler: { (alert) in
        self.rate = 1.25
        self.rateSeekSpeed(self.rate)
    }))
    alert.addAction(UIAlertAction(title: "1x", style: .default, handler: { (alert) in
        self.rate = 1
        self.rateSeekSpeed(self.rate)
    }))
    alert.addAction(UIAlertAction(title: "0.75x", style: .default, handler: { (alert) in
        self.rate = 0.75
        self.rateSeekSpeed(self.rate)
    }))
    alert.addAction(UIAlertAction(title: "0.5x", style: .default, handler: { (alert) in
        self.rate = 0.5
        self.rateSeekSpeed(self.rate)
    }))
    
    alert.addAction(UIAlertAction(title: "Отмена", style: .cancel, handler: nil))
    alert.view.tintColor = .black
    present(alert, animated: true, completion: nil)
}

No such module 'RxCocoa'

I can't run successfully because xcode reports an error “No such module 'RxCocoa”, Excuse me, what should I do?

Loading state although the player is playing

I have the problem that the following RxMusicPlayer.Status are deliver for remote audio url after play tap:

  • loading
  • loading
  • playing
  • loading

However, the player is playing and is not in the prefetching state (see last state). Why is this happening?

RxMusic

Hi
I have 2 pages, one song player and the other song list.
I want to play a new song from this list every time and replace the new one with the previous one.

But both songs play together

New Feature

Hello. I have been using your player for a very long time. my users started to write that they want to have their own equalizer setting. I would like you to add an equalizer to the player, if you have time, I can help with this

Keeping progress

Hi guy! I will be grateful if you will make the functionality of remembering the second when you close the application or when switching to another track, since this functionality is needed in my project. As an example via CoreData

Bug: player auto play next audio

I have two screens A and B in a project.
A contains playerA and B contains playerB.
PlayerB plays the sound at index 5 and I stop it.
PlayerA plays the sound at index 1.
After the sound at index 1 end, Player A plays the sound at index 2 and playerB automatically next to 6.

I'm not certain that I'm using this library properly.
If it's a lib bug, please fix it.
If not, please show me how to fix it.

Thanks

Update artwork

Hello. how can i update the player's artwork for example without stopping it and deleting and adding the same url?

Player seek

Hi @yoheimuta !

I want to do through SoreDate to do time saving
In the musicPlayer.player method? .Rx.currentItemTime (), as I understand it, it is best to save data every 5 seconds
var i = Int (($ 0.0? .seconds!)!)% 5

Снимок экрана 2020-10-27 в 11 13 02

If I'm wrong, correct me and tell me how best to save seconds here

The second question is to start at a certain second
i start in musicPlayer.player? .run (cmd: cmd)

Снимок экрана 2020-10-27 в 11 12 51

But then there is a problem with the fact that if there is a saved time for example 5 seconds and I am now at 9 seconds and pressed stop, then pressing start I start from 5 seconds

If I'm wrong, correct me and tell me how best to start here

My code is not working correctly

I would be grateful for your help 👍

Error

Hey! When installing Pod issues an error when compiling the project
Снимок экрана 2021-03-31 в 13 06 57
Снимок экрана 2021-03-31 в 13 08 21
Снимок экрана 2021-03-31 в 13 08 32

Bug: slider is not updated

I noticed that when the audio comes to an end (for example: there are 15 seconds left) and at the same time open a new screen as a present, then the scale will stop at the same place and will not move, and also when moving to the next track, it will stand in the same position

HELP

Hey!
I faced such problem
I have an audio array {A, B, C, E, G} and I want to add a new array {D, F} to it, not only at the end, but also in alphabetical order. Example {A, B, C, D, E, F, G}

For a more intelligible concept, I will attach the video below.

https://vimeo.com/609763110

at the moment I'm doing just apped

Button action

Hi, how can I activate a command without clicking on a button, but for example a table cell, since there is a video and I want to stop the player?
image

Audio duplication error

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.

previous button

But you can fix the button back so that it does not wind the audio up to 0 second, and immediately switches to the previous track, if there is one

playback speed

Hi

How can I change the playback speed of the player? x1, x1.5, x2

How can I start the player from a certain second?
Start player to 1min25sek

"Boost volume" functionality

Hey! Please add the "Boost volume" functionality, here is an example of how it should work

var boostVolume: Bool = false {
didSet {
self.audioPlayer.volume = self.boostVolume
? 2
: 1
}
}

Add MP3 Urls by Button while playing

How can I add a mp3 url by button, if the player plays - without seek difference?

my solution

fileUrlRelay
  .skip(1)
  .distinctUntilChanged()
  .compactMap({ $0 })
  .map({ DownloadHelper.storedUrl(url: $0) ?? $0 })
  .map({ RxMusicPlayerItem(url: $0, meta: .init()) })
  .map({ [$0] })
  .bind(to: player.queuedItemsRelay)
  .disposed(by: bag)

but I can not access to queuedItemsRelay.

I'm emmiting .stop and keeps de reference in background

Hello,

I have an issue. I have a screen where I instantiate a RxMusiclPlayer with the Item that I need.
It works perfectly.
But once I leave the viewscreen If I lock the screen keeps showing the panel to reproduce the audio although I have :
viewDidDisappear{

        self.emitCommand.onNext(.stop)
        self.player?.player?.pause()
        self.player?.player?.replaceCurrentItem(with: nil)
        self.player = nil

}

Also, I go to another screen that instantiates another RxMusicPlayer. it works perfect but if I lock the screen the player panel over there plays two tracks at the same time.

So it's like AVplayer panel from the lock screen keeps a reference two both RxMusicPlayer?

how can I update all URLs in the player?

I can edit the audio metadata in my application, how can I update all URLs in the player?

Most likely, new functionality is needed, since after editing I delete the original url and add a new url

Add more functionality to rewind audio (15, 30) seconds

Add more functionality to rewind audio (15, 30) seconds, so that the user can assign it himself how much it would be convenient for him

MPRemoteCommandCenter.shared().skipForwardCommand and
MPRemoteCommandCenter.shared().skipBackwardCommand

Boost volume

Hey!
I poked around in your code and implemented the Sound Boost function
Default value player.volume = 1, can be changed to 2

If interested, I can throw off the implementation so that you can release an update

Repeat mode settings

Thanks so much for such a nice library.
Is it possible to do something like numberOfLoops from AVAudioPlayer? For both modes — repeatOne and repeatAll?

karaoke function

Hey! Do you know how to implement the karaoke function?

An example like Apple Music, when the text moves along with the audio

Put all the songs in UITableView

Hi
I have a question, hope you can help, I wanted to put all songs in UITable view and play only if the cell is tapped or the button inside the cell is tapped. Is that possible RxMusicPlayer??

Thanks

New functionality: jump to the desired second

Please add two new functions to the player: 1) jump to the required second 2) jump back to the required number of seconds

  1. func jumpTo(_ time: Double)
  2. func jumpBy(_ direction: Double)

FadeIn and FadeOut

Hello. there is a new idea for the player FadeIn and FadeOut, by default it does not fade out, but the user will call it there for example: fade out for the last 1.5 seconds

Impossible to replace playing items

If I need to completely replace playing items, the only way to do this seems to be:

player.stop()

 self.player.queuedItems.enumerated().forEach { idx, value in   
      try? self.player.remove(at: idx)   
}

 self.player.append(items: newItems)

But this does not work. The remove will throw error when index will match playIndex, so if I'd like to call play(at: index_from_the_new_items) it will not be the one I want.

So, what is the solution to stop the player, replaces all items. add new ones and play from desired index ?

Wrong time display

Hey, I noticed that the current time is out of sync with the remaining time

Upload.from.GitHub.for.iOS.MOV

Dear author,

This is a really great RxPlayer. But how can I play the music when the first time I come in this player ViewController, Or control the player in other View?

Bug

1 Crash

Crashed: RxMusicPlayerSerialQueue
0  RxMusicPlayer                  0xb04c $s13RxMusicPlayerAAC4play33_12259D9EB31950C5AF396C6D068D9253LL7atIndex0A5Swift10ObservableCyytGSi_tF + 632
1  RxMusicPlayer                  0xaaec $s13RxMusicPlayerAAC10runCommand33_12259D9EB31950C5AF396C6D068D9253LL3cmd0A5Swift10ObservableCyytGAB0E0O_tFAISbcfU_ + 136
2  RxMusicPlayer                  0x12918 $s13RxMusicPlayerAAC10runCommand33_12259D9EB31950C5AF396C6D068D9253LL3cmd0A5Swift10ObservableCyytGAB0E0O_tFAISbcfU_TA + 20
3  RxSwift                        0x79e08 $s7RxSwift13MapSwitchSink33_329C7BE5E760F8B243CD9FB0AF8C1442LLC07performC0yq_xKF + 32
4  RxSwift                        0x795f0 $s7RxSwift10SwitchSink33_329C7BE5E760F8B243CD9FB0AF8C1442LLC2onyyAA5EventOyxGF + 556
5  RxSwift                        0x79a74 $s7RxSwift10SwitchSink33_329C7BE5E760F8B243CD9FB0AF8C1442LLCyxq_q0_GAA12ObserverTypeA2aFP2onyyAA5EventOy7ElementQzGFTW + 20
6  RxSwift                        0x55580 $s7RxSwift32ObserveOnSerialDispatchQueueSink33_277A93ABA8477198C125F3F26B2D4B62LLC9scheduler8observer6cancelADyxGAA0efG9SchedulerC_xAA10Cancelable_ptcfcAA10Disposable_pAH4sink_AA5EventOy7ElementQzG5eventt_tcfU_ + 508
7  RxSwift                        0x558d8 $s7RxSwift32ObserveOnSerialDispatchQueueSink33_277A93ABA8477198C125F3F26B2D4B62LLCyxGAA5EventOy7ElementAA12ObserverTypePQzGAA10Disposable_pIeggnr_AE_ALtAaM_pIegnr_AaIRzlTR + 160
8  RxSwift                        0x33604 $s7RxSwift26DispatchQueueConfigurationV8schedule_6actionAA10Disposable_px_AaF_pxctlFyyYbcfU_ + 104
9  RxSwift                        0x29618 $sIegh_IeyBh_TR + 28
10 libdispatch.dylib              0x26a8 _dispatch_call_block_and_release + 32

Second crash

Crashed: com.apple.avfoundation.avasset.completionsQueue
0  RxMusicPlayer                  0xb2d4 $s13RxMusicPlayerAAC4play33_12259D9EB31950C5AF396C6D068D9253LL7atIndex0A5Swift10ObservableCyytGSi_tFAiA0abC4ItemCSgcfU_ + 644
1  RxMusicPlayer                  0x128fc $s13RxMusicPlayerAAC4play33_12259D9EB31950C5AF396C6D068D9253LL7atIndex0A5Swift10ObservableCyytGSi_tFAiA0abC4ItemCSgcfU_TA + 16
2  RxSwift                        0x79e08 $s7RxSwift13MapSwitchSink33_329C7BE5E760F8B243CD9FB0AF8C1442LLC07performC0yq_xKF + 32
3  RxSwift                        0x795f0 $s7RxSwift10SwitchSink33_329C7BE5E760F8B243CD9FB0AF8C1442LLC2onyyAA5EventOyxGF + 556
4  RxSwift                        0x79a74 $s7RxSwift10SwitchSink33_329C7BE5E760F8B243CD9FB0AF8C1442LLCyxq_q0_GAA12ObserverTypeA2aFP2onyyAA5EventOy7ElementQzGFTW + 20
5  RxSwift                        0x75504 $s7RxSwift4SinkC9forwardOnyyAA5EventOy7ElementQzGF + 112
6  RxSwift                        0x29ac4 $s7RxSwift23AnonymousObservableSink33_95EBF5692819D58425EC2DD0512D115ALLC2onyyAA5EventOy7ElementQzGF + 324
7  RxSwift                        0x29c28 $s7RxSwift23AnonymousObservableSink33_95EBF5692819D58425EC2DD0512D115ALLCyxGAA12ObserverTypeA2aFP2onyyAA5EventOy7ElementQzGFTW + 20
8  RxSwift                        0x6774 $s7RxSwift11AnyObserverVyACyxGqd__c7ElementQyd__RszAA0D4TypeRd__lufcyAA5EventOyxGcqd__cfu_yAJcfu0_TA + 44
9  RxSwift                        0x6230 $s7RxSwift11AnyObserverV2onyyAA5EventOyxGF + 20
10 RxSwift                        0x729e8 $s7RxSwift21PrimitiveSequenceTypePA2A11SingleTraitO0G0RtzrlE6create9subscribeAA0cD0VyAE7ElementQzGAA10Disposable_pys6ResultOyAMs5Error_pGcc_tFZAaO_pAA11AnyObserverVyAMGcfU_yAScfU_ + 476

Button action tap

Hi, how can I activate a command without clicking on a button, but for example a table cell, since there is a video and I want to stop the player?
image

"Module compiled with Swift 5.0 cannot be imported by the Swift 5.1.2 compiler" error when building Example

I got an error: /Users/Me/Documents/gist/RxMusicPlayer/RxMusicPlayer/RxMusicPlayer+Rx.swift:10:8: Module compiled with Swift 5.0 cannot be imported by the Swift 5.1.2 compiler: /Users/Me/Documents/gist/RxMusicPlayer/Carthage/Build/iOS/RxCocoa.framework/Modules/RxCocoa.swiftmodule/arm64.swiftmodule

And after I update carthage file with RxSwift version 5.0.0, still the same.
I have been searched some solutions but not clear for me 😭
Is this problem means the RxSwift framework is using Swift 5.1.2, so I have to upgrade my Swift version from 5.0.0?

audio overlay

Users noticed that the audio overlaps, can I somehow fix this? All the code is like yours. Example: while the audio has not loaded, the prohibition on downloading another

BUG RxMusicPlayer.play(atIndex:)

Hello!
Most users notice this bug.

I know for sure that the problem is not in the index being transmitted, since I sent analytics and monitored this bug

Any thoughts on how to fix it?

I can't reproduce this bug :(

Снимок экрана 2024-04-18 в 13 46 49 Снимок экрана 2024-04-18 в 13 48 29

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.