I have a UISlider added to my app and I am trying to figure out how to change the volume of the music playing. The music playing is being pulled from the iTunes Library using MPMusicPlayerController
Here is the viewDidLoad function with to show how I get the music from the iTunes Music library.
var musicPlayer = MPMusicPlayerController()
override func viewDidLoad() {
super.viewDidLoad()
musicPlayer = MPMusicPlayerController.systemMusicPlayer()
//Changing the Play/Pause Button
if musicPlayer.playbackState == MPMusicPlaybackState.Playing {
playPauseButton.setImage(UIImage(named: "Pause"), forState: UIControlState.Normal)
//Artwork
let currentItem: MPMediaItem = musicPlayer.nowPlayingItem!
let artwork: MPMediaItemArtwork = currentItem.valueForProperty(MPMediaItemPropertyArtwork) as! MPMediaItemArtwork
let artworkImage = artwork.imageWithSize(CGSize(width: self.view.frame.width, height: 372.0))
musicPlayerArtwork.image = artworkImage
musicBottomBackground.image = artworkImage
//Titel
let titleString: String = currentItem.valueForProperty(MPMediaItemPropertyTitle) as! String
songTitle.text = titleString as String
//Artist & Album
let artistString: String = currentItem.valueForProperty(MPMediaItemPropertyArtist) as! String
let albumString: String = currentItem.valueForProperty(MPMediaItemPropertyAlbumTitle) as! String
albumAndArtistTitle.text = "\(artistString) - \(albumString)"
} else {
playPauseButton.setImage(UIImage(named: "Play"), forState: UIControlState.Normal)
}
}
// I have IBAction functions linked to controlling the playbackk and pause functions that look like this one to play the music
func startPlayMusic() {
if self.musicPlayer.playbackState == MPMusicPlaybackState.Paused {
self.musicPlayer.play()
}
}
I have the slider hooked up with an IBAction and IBOutlet and have the following function to attempt adjusting the volume it:
var audioPlayer = AVAudioPlayer()
#IBAction func volumeSliderChanging(sender: UISlider) {
audioPlayer.volume = sender.value
}
Each time I try running this and moving the slider I get the following error
Any ideas what I could be doing wrong?
Your audioPlayer was not properly initiated. You should not call AVAudioPlayer(). Refer to the documentation for list of initializers that you should use. Here's an example:
let url = NSBundle.mainBundle().URLForResource("mySong", withExtension: "mp3")!
self.audioPlayer = try! AVAudioPlayer(contentsOfURL: url)
Related
As you see, I want to hide these three buttons on the Picture in Picture, Is there any solution?
Here is my pip code:
private lazy var playerLayer: AVPlayerLayer = {
let layer = AVPlayerLayer()
let mp4Video = Bundle.main.url(forResource: "v1", withExtension: "MP4")
let asset = AVAsset.init(url: mp4Video!)
let playerItem = AVPlayerItem.init(asset: asset)
let player = AVPlayer.init(playerItem: playerItem)
layer.player = player
player.isMuted = true
player.allowsExternalPlayback = false
return layer
}()
private lazy var pipVC: AVPictureInPictureController = {
let vc = AVPictureInPictureController.init(playerLayer: playerLayer)
vc?.delegate = self
return vc!
}()
You can hide at least the seek buttons via:
vc.requiresLinearPlayback = true
Where vc is your AVPictureInPictureController instance.
If you have already found a way how to hide the play button, please share it.
Use KVC:
pipController.setValue(1, forKey: "controlsStyle")
For more detail about custom PiP, you can take a look at:
https://github.com/CaiWanFeng/PiP
if #available(iOS 14.0, *) {
//Hide the seekbar
pipController?.requiresLinearPlayback = true
} else {
// Fallback on earlier versions
}
//Hide the play/pause controls
pipController?.setValue(1, forKey: "controlsStyle")
So I have a UIButton whose imageView is set to an image using a download URL. For this purpose I use SDWebImage.
Problem is, when I press the delete button, I want the image to completely disappear but I guess it does not work because the image is still being retrieved from cache. How do I solve this?
class ViewController: UIViewController{
var profileButton: UIButton = {
let button = UIButton(type: .system)
return button
}()
var user: User?
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(profileButton)
profileButton.addTarget(self, action: #selector(handleDelete), for: .touchUpInside)
self.loadUserPhotos()
}
fileprivate func loadUserPhotos(){
let profileImageUrl = self.user?.profileImageUrl1
if let imageUrl = profileImageUrl, let url = URL(string: imageUrl){
SDWebImageManager.shared().loadImage(with: url, options: .continueInBackground, progress: nil) { (image, _, _, _, _, _) in
self.profileButton.setImage(image?.withRenderingMode(.alwaysOriginal), for: .normal)
}
}
}
#objc func handleDelete(){
self.user?.profileImageUrl1 = ""
self.profileButton.imageView?.image = nil
self.loadUserPhotos()
}
}
To remove the image from UIButton you need to mention the state as well.
self.profileButton.setImage(nil, for: .normal)
You can use :
SDImageCache.shared.removeImage(forKey: url?.description, withCompletion: nil)
I am trying to control the audio with the UISlider, the print() statement prints the correct value and the app doesn't crash, however when I try to grab it and move the slider (the thumb tint), the UISlider doesn't slide but just moves a bit when I try to slide it ( like a tap ).
When I comment the 6th row the slider response correctly (But of course nothing happens).
var playerItem : AVPlayerItem?
var player : AVPlayer?
#IBAction func adjustSongProgress(_ sender: UISlider) {
player?.pause()
let floatTime = Float(CMTimeGetSeconds(player!.currentTime()))
sliderProgressOutlet.value = floatTime
print(floatTime)
player?.play()
}
Fixed it by deleting AVPlayer and changing AVPlayerItem to AVAudioPlayer, then putting the song URL into data : `
//DOWNLOADS THE SONG IN THE URL AND PUTS IT IN DATA
var task: URLSessionTask? = nil
if let songUrl = songUrl {
task = URLSession.shared.dataTask(with: songUrl, completionHandler: { data, response, error in
// SELF.PLAYER IS STRONG PROPERTY
if let data = data {
self.playerItem = try? AVAudioPlayer(data: data)
self.playPause()
DispatchQueue.main.async {
self.sliderProgress()
}
}
})
task?.resume()`
Then I changed UISlider IBAction Value Changed to Touch Down and Touch Up Inside when I connected it to the ViewController:
// TOUCH DOWN
#IBAction func SliderTouchDown(_ sender: UISlider) {
playerItem?.pause()
}
//TOUCH UP INSIDE
#IBAction func SliderTouchUpInside(_ sender: UISlider) {
playerItem?.currentTime = TimeInterval(sliderProgressOutlet.value)
playerItem?.play()
}
iOS Slider takes value between 0 to 1. if CMTimeGetSeconds return value outside from 0 to 1 it will not set properly.
therefor you have to convert your Time range to slider range.
for ex : your video/audio length is 120 second and you want to move slider to 30 second.than you can calculate new value using below function.
OldRange = (OldMax - OldMin)
NewRange = NewMax - NewMin
NewValue = (((OldValue - OldMin) * NewRange) / OldRange) + NewMin
oldRange = 120 - 0
newRange = 1 - 0
New value = (30-0)*1/120+0 = 0.25
I am trying to make a music player and currently I am stuck on filtering and prepending an album to the playlist.
What I am trying to do is play music on shuffle and then when a user taps a button continue playing songs only from the album currently playing, when it is finished I want to know its finished so I can change UI elements and then go on to play any and all music from the library.
However, what happens is it will play the rest of the songs from the album THEN when it has exhausted all the songs from that album will play a random song then after that one random song will go back to that album ..go through its entirety and then play random songs. Once in a blue moon after it finishes the album it will just play random songs.
In a singleton I have
func getAllSongs(completion: #escaping (_ songs: [MPMediaItem]?) -> Void) {
MPMediaLibrary.requestAuthorization { (status) in
if status == .authorized {
let query = MPMediaQuery()
let mediaTypeMusic = MPMediaType.music
let audioFilter = MPMediaPropertyPredicate(value: mediaTypeMusic.rawValue, forProperty: MPMediaItemPropertyMediaType, comparisonType: MPMediaPredicateComparison.equalTo)
query.addFilterPredicate(audioFilter)
let songs = query.items
completion(songs)
} else {
completion(nil)
}
}
}
func getSongsWithCurrentAlbumFor(item: MPMediaItem) -> MPMediaQuery {
let albumFilter = MPMediaPropertyPredicate(value: item.albumTitle, forProperty: MPMediaItemPropertyAlbumTitle, comparisonType: MPMediaPredicateComparison.equalTo)
let predicates: Set<MPMediaPropertyPredicate> = [albumFilter]
let query = MPMediaQuery(filterPredicates: predicates)
query.addFilterPredicate(albumFilter)
return query
}
In my VC to set up the audio I use
let mediaPlayer = MPMusicPlayerApplicationController.applicationMusicPlayer
func setUpAudioPlayerAndGetSongsShuffled() {
try? AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategorySoloAmbient)
try? AVAudioSession.sharedInstance().setActive(true)
MBProgressHUD.showAdded(to: view, animated: true)
MediaManager.shared.getAllSongs { (songs) in
guard let theSongs = songs else {
return
}
self.newSongs = theSongs.filter({ (item) -> Bool in
return !self.playedSongs.contains(item)
})
self.mediaPlayer.setQueue(with: MPMediaItemCollection(items: self.newSongs))
self.mediaPlayer.shuffleMode = .songs
self.mediaPlayer.repeatMode = .none
self.mediaPlayer.prepareToPlay(completionHandler: { (error) in
DispatchQueue.main.async {
MBProgressHUD.hide(for: self.view, animated: true)
}
})
}
}
When the user taps the button to continue playing songs only from that album I use
if let nowPlaying = mediaPlayer.nowPlayingItem {
let albumQuery = MediaManager.shared.getSongsWithCurrentAlbumFor(item: nowPlaying)
print("\(albumQuery)")
let descriptor = MPMusicPlayerMediaItemQueueDescriptor(query: albumQuery)
mediaPlayer.prepend(descriptor)
}
Upon rereading the documentation I notice it says that I should change to
let mediaPlayer = MPMusicPlayerApplicationController. applicationQueuePlayer
I cannot figure out how to know when an album has been exhausted and then continue playing the rest of the music library.
it would be great to know if the album does not have any other items so that the user could not press the button to play more from the album
I discovered my mistakes. I was putting my logic in the wrong place
In my singleton I added
func hasPlayedAllSongsFromAlbumFor(song: MPMediaItem) -> Bool {
if let allSongsInAlbum = getSongsWithCurrentAlbumFor(item: song).items {
return lockedSongsContains(songs: allSongsInAlbum)
}
return true
}
func lockedSongsContains(songs: [MPMediaItem]) -> Bool {
for aSong in songs {
if !lockedSongs.contains(aSong) {
return false
}
}
return true
}
I needed to use a Notification
I created a func that has a notification in the ViewDidLoad called songChanged
In the songChanged func I have
if albumIsLocked && MediaManager.shared.hasPlayedAllSongsFromAlbumFor(song: nowPlaying) {
albumLockButtonTapped(albumLockIconButton)
MediaManager.shared.lockedSongs.removeAll()
}
On the album lock button I now use
if let nowPlaying = mediaPlayer.nowPlayingItem {
if sender.isSelected {
sender.isSelected = false
albumIsLocked = false
let lockRemovedQuery = MediaManager.shared.removeAlbumLockFor(item: nowPlaying)
let lockRemovedDescriptor = MPMusicPlayerMediaItemQueueDescriptor(query: lockRemovedQuery)
mediaPlayer.prepend(lockRemovedDescriptor)
mediaPlayer.prepareToPlay()
} else {
sender.isSelected = true
albumIsLocked = true
let albumQuery = MediaManager.shared.getSongsWithCurrentAlbumFor(item: nowPlaying)
let albumDescriptor = MPMusicPlayerMediaItemQueueDescriptor(query: albumQuery)
mediaPlayer.prepend(albumDescriptor)
mediaPlayer.prepareToPlay()
}
}
I want to allow background audio while the app is not in focus. I currently have this code, which should allow that:
do {
try AKSettings.setSession(category: .playback, with: .mixWithOthers)
} catch {
print("error")
}
AKSettings.playbackWhileMuted = true
I also have the setting 'Audio, Airplay and Picture in Picture' enabled in capabilities settings. However, when I press the home button on my device the audio doesn't keep playing. What am I doing wrong? I am using AudioKit to produce sounds if that matters.
I am using a singleton to house all of the AudioKit components which I named AudioPlayer.swift. Here is what I have in my AudioPlayer.swift singleton file:
class AudioPlayer: NSObject {
var currentFrequency = String()
var soundIsPlaying = false
var leftOscillator = AKOscillator()
var rightOscillator = AKOscillator()
var rain = try! AKAudioFile()
var rainPlayer: AKAudioPlayer!
var envelope = AKAmplitudeEnvelope()
override init() {
super.init()
do {
try AKSettings.setSession(category: .playback, with: .mixWithOthers)
} catch {
print("error")
}
AKSettings.playbackWhileMuted = true
AudioKit.output = envelope
AudioKit.start()
}
func setupFrequency(left: AKOscillator, right: AKOscillator, frequency: String) {
currentFrequency = frequency
leftOscillator = left
rightOscillator = right
let leftPanner = AKPanner(leftOscillator)
leftPanner.pan = -1
let rightPanner = AKPanner(rightOscillator)
rightPanner.pan = 1
//Set up rain and rainPlayer
do {
rain = try AKAudioFile(readFileName: "rain.wav")
rainPlayer = try AKAudioPlayer(file: rain, looping: true, deferBuffering: false, completionHandler: nil)
} catch { print(error) }
let mixer = AKMixer(leftPanner, rightPanner, rainPlayer)
//Put mixer in sound envelope
envelope = AKAmplitudeEnvelope(mixer)
envelope.attackDuration = 2.0
envelope.decayDuration = 0
envelope.sustainLevel = 1
envelope.releaseDuration = 0.2
//Start AudioKit stuff
AudioKit.output = envelope
AudioKit.start()
leftOscillator.start()
rightOscillator.start()
rainPlayer.start()
envelope.start()
soundIsPlaying = true
}
}
And here is an example of one of my sound effect view controllers, which reference the AudioKit singleton to send it a certain frequency (I have about a dozen of these view controllers, each with its own frequency settings):
class CalmView: UIViewController {
let leftOscillator = AKOscillator()
let rightOscillator = AKOscillator()
override func viewDidLoad() {
super.viewDidLoad()
leftOscillator.amplitude = 0.3
leftOscillator.frequency = 220
rightOscillator.amplitude = 0.3
rightOscillator.frequency = 230
}
#IBAction func playSound(_ sender: Any) {
if shared.soundIsPlaying == false {
AudioKit.stop()
shared.setupFrequency(left: leftOscillator, right: rightOscillator, frequency: "Calm")
} else if shared.soundIsPlaying == true && shared.currentFrequency != "Calm" {
AudioKit.stop()
shared.leftOscillator.stop()
shared.rightOscillator.stop()
shared.rainPlayer.stop()
shared.envelope.stop()
shared.setupFrequency(left: leftOscillator, right: rightOscillator, frequency: "Calm")
} else {
shared.soundIsPlaying = false
shared.envelope.stop()
}
}
}
I instantiated the AudioPlayer singleton in my ViewController.swift file.
It depends on when you are doing your configuration in relation to when AudioKit is started. If you're using AudioKit you should be using its AKSettings to manage your session category. Basically not only the playback category but also mixWithOthers. By default, does this:
/// Set the audio session type
#objc open static func setSession(category: SessionCategory,
with options: AVAudioSessionCategoryOptions = [.mixWithOthers]) throws {
So you'd do something like this in your ViewController:
do {
if #available(iOS 10.0, *) {
try AKSettings.setSession(category: .playAndRecord, with: [.defaultToSpeaker, .allowBluetooth, .allowBluetoothA2DP])
} else {
// Fallback on earlier versions
}
} catch {
print("Errored setting category.")
}
So I think its a matter of getting that straight. It might also help to have inter-app audio set up. If you still have trouble and provide more information, I can help more, but this is as good an answer as I can muster based on the info you've given so far.