Swift 4 The UISlider with AVPlayer doesn't slide - ios

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

Related

update control center playback seek when player view controller of slider value changed

I got to change the slider value when the control center of change playback media position is changed...
commandCenter.changePlaybackPositionCommand.addTarget { (event) -> MPRemoteCommandHandlerStatus in
let event = event as! MPChangePlaybackPositionCommandEvent
let time = CMTime(seconds: event.positionTime, preferredTimescale: self.player.currentTime().timescale)
self.player.seek(to: time)
return .success;
}
and
#IBAction func handleSliderChange(_ sender: UISlider) {
let seconds : Int64 = Int64(sender.value)
let targetTime:CMTime = CMTimeMake(seconds, 1)
myPlayer.player.seek(to: targetTime)
}
#IBAction func sliderTapped(_ sender: UILongPressGestureRecognizer) {
if let slider = sender.view as? UISlider {
if slider.isHighlighted { return }
let point = sender.location(in: slider)
let percentage = Float(point.x / slider.bounds.width)
let delta = percentage * (slider.maximumValue - slider.minimumValue)
let value = slider.minimumValue + delta
slider.setValue(value, animated: false)
let seconds : Int64 = Int64(value)
let targetTime: CMTime = CMTimeMake(seconds, 1)
self.myPlayer.player.seek(to: targetTime)
}
}
however, in inverse method, I have no idea what to do. I want to update current time when the slider value is changed.

Updating a variable in a function driven by Timer

I have the below function that works properly when a button is switched to activate it. I want to add a variable that gives each message it's current message number starting from 1. I've tried different methods of setting / updating the value but none of it helped with updating.
I'm very new to Swift / iOS development so I'm sure there is something I'm missing. What I do know is that the message prints to console repeatedly till the button is switched off and the Timer is what enables it to continuously run.
#IBOutlet weak var stateLabel: UILabel!
//Starts / Stops recording of sensor data via a switch
#IBAction func stateChange(_ sender: UISwitch) {
if sender.isOn == true {
startSensorData()
stateLabel.text = "Stop"
} else {
stopSensorData()
stateLabel.text = "Start"
}
}
func startSensorData() {
print("Start Capturing Sensor Data")
// Making sure sensors are available
if self.motionManager.isAccelerometerAvailable, self.motionManager.isGyroAvailable {
// Setting the frequency required for data session
self.motionManager.accelerometerUpdateInterval = 1.0 / 3.0
self.motionManager.gyroUpdateInterval = 1.0 / 3.0
// Start sensor updates
self.motionManager.startAccelerometerUpdates()
self.motionManager.startGyroUpdates()
// Configure a timer to fetch the data.
self.motionUpdateTimer = Timer.scheduledTimer(withTimeInterval: 1.0/3.0, repeats: true, block: { (timer1) in
// Get the motion data.
var loggingSample = 1
if let accelData = self.motionManager.accelerometerData, let gyroData = self.motionManager.gyroData {
let accelX = accelData.acceleration.x
let accelY = accelData.acceleration.y
let accelZ = accelData.acceleration.z
let gyroX = gyroData.rotationRate.x
let gyroY = gyroData.rotationRate.y
let gyroZ = gyroData.rotationRate.z
let message = "\(Date().timeIntervalSince1970),\(self.device_id),\(loggingSample),\(accelX),\(accelY),\(accelZ),\(gyroX),\(gyroY),\(gyroZ),Processing"
print(message)
loggingSample += 1
}
}
)}
}
You keep getting a value of 1 for loggingSample because you are using a local variable that gets created as 1 each time.
All you need to do is move the declaration of loggingSample to be outside the function so it is a class property.
Move the line:
var loggingSample = 1
outside the function so it is next to your outlets and other properties.

Play/Pause and Elapsed Time not updating in iOS command center properly

I have a video player that can play from the iOS command center and lock screen. When I toggle a play/pause button in my app, it should update the play/pause button in the command center (MPRemoteCommandCenter) by updating the nowPlayingInfo (MPNowPlayingInfoCenter). I'm not sure why it's not updating.
For example, if I pause the video with a custom button in my app, the command center still shows the pause button (meaning the video is still playing which is wrong.)
This is how I update the nowPlayingInfo:
func updateMPNowPlayingInforCenterMetadata() {
guard video != nil else {
nowPlayingInfoCenter.nowPlayingInfo = nil
return
}
var nowPlayingInfo = nowPlayingInfoCenter.nowPlayingInfo ?? [String: Any]()
let image: UIImage
if let placeholderLocalURL = video.placeholderLocalURL, let placeholderImage = UIImage(contentsOfFile: placeholderLocalURL.path) {
image = placeholderImage
} else {
image = UIImage()
}
let artwork = MPMediaItemArtwork(boundsSize: image.size, requestHandler: { _ -> UIImage in
return image
})
nowPlayingInfo[MPMediaItemPropertyTitle] = video.title
nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = video.creator?.name ?? " "
nowPlayingInfo[MPMediaItemPropertyArtwork] = artwork
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = Float(video.duration)
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = Float(currentTime) // CMTimeGetSeconds(player.currentItem!.currentTime())
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player.rate
nowPlayingInfo[MPNowPlayingInfoPropertyDefaultPlaybackRate] = player.rate
nowPlayingInfoCenter.nowPlayingInfo = nowPlayingInfo
if player.rate == 0.0 {
state = .paused
} else {
state = .playing
}
}
With KVO, when the player's rate changes, I call this function:
// MARK: - Key-Value Observing Method
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard context == &assetPlaybackManagerKVOContext else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
} else if keyPath == #keyPath(AVPlayer.rate) {
updateMPNowPlayingInforCenterMetadata()
}
}
Any thoughts?
UPDATE
I found a solution though but not perfect in my case. So in my app I have 2 view controller's. Let's call them FeedVC and PlayerVC. So FeedVC has AVPlayer's that are always playing but are muted. If you click on one of them, then the PlayerVC is created and plays the full video. If I pause the AVPlayer's in FeedVC before going to PlayerVC then the "play/pause" button in the NowPlayingInfoCenter works perfectly!
Is there a way to make this work without having to pause the videos in the FeedVC?
Another issue is that the elapsed time keeps counting if I don't pause the players in the FeedVC. It seems that if multiple players are playing, the play/pause button and elapsed time are incorrect.
When you setting the dictionary for nowPlayingInfo you will want to set the MPNowPlayingInfoPropertyPlaybackRate value appropriately. The MPNowPlayingInfoCenter is expecting either a 1.0 (playing) or 0.0 (not playing) value as a Double wrapped in a NSNumber object. Below you will find the code for how I'm setting the nowPlayingInfo in my project.
func setNowPlayingMediaWith(song: SUSong, currentTime: Double?) {
var playingInfo:[String: Any] = [:]
if let title = song.title {
playingInfo[MPMediaItemPropertyTitle] = title
}
if let songID = song.id {
playingInfo[MPMediaItemPropertyPersistentID] = songID
}
if let artist = song.artist, let artistName = artist.name {
playingInfo[MPMediaItemPropertyArtist] = artistName
}
if let album = song.album, let albumTitle = album.title {
var artwork:MPMediaItemArtwork? = nil
if let album = song.album, let artworkData = album.artwork {
artwork = MPMediaItemArtwork(boundsSize: Constants.Library.Albums.thumbSize, requestHandler: { (size) -> UIImage in
return UIImage(data: artworkData as Data)!
})
}
if artwork != nil {
playingInfo[MPMediaItemPropertyArtwork] = artwork!
}
playingInfo[MPMediaItemPropertyAlbumTitle] = albumTitle
}
var rate:Double = 0.0
if let time = currentTime {
playingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = time
playingInfo[MPMediaItemPropertyPlaybackDuration] = song.duration
rate = 1.0
}
playingInfo[MPNowPlayingInfoPropertyPlaybackRate] = NSNumber(value: rate)
playingInfo[MPNowPlayingInfoPropertyMediaType] = NSNumber(value: MPNowPlayingInfoMediaType.audio.rawValue)
MPNowPlayingInfoCenter.default().nowPlayingInfo = playingInfo
}
In this method I am passing the song that my player is has currently loaded. Whenever the user chooses to play or pause I call setNowPlayingMediaWith(song:currentTime:) to update the device's control console.
I keep track of the currentTime (Double) as a property of my player. If there is a currentTime passed in that means we are meant to be playing, so set the MPNowPlayingInfoPropertyPlaybackRate to 1.0 and set the MPNowPlayingInfoPropertyElapsedPlaybackTime to currentTime. This will set the current time to start automatically playing in the device's control console.
Likewise, if there is no currentTime passed then we have stopped or paused. In this case we set the MPNowPlayingInfoPropertyPlaybackRate to 0.0 and we do not include the MPNowPlayingInfoPropertyElapsedPlaybackTime.
Download my app to see this in action.
EDIT (answer to comments)
"Is there a way to make this work without having to pause the videos in the FeedVC"
Without seeing your code it is difficult to give you a definite answer. It would make sense though to pause any ongoing media before starting your PlayerVC's media.
"the elapsed time keeps counting"
The elapsed time will countdown the elapsed time based on an NSTimer property on NowPlayingInfoCenter. This timer will stop only when the timers value has reached the value you set in MPMediaItemPropertyPlaybackDuration, or when you update the MPNowPlayingInfoPropertyElapsedPlaybackTime.
My suggestion is to write a method that "clears out" the NowPlayingInfoCenter. This method should set the will set all of key values to either 0.0 or nil respectively. Call this "clear out" method each time before you play your media in PlayerVC. Then once you play from PlayerVC, set the NowPlayingInfoCenter key values like you would in the method I pasted in my answer to set the new values for the new playing media.

How to implement sender.selected if statement into UISlider?

I am trying to create a slider that changes audio dependent on the value of the slider. So, for example, if the slider value is 0, no audio plays. If the value is 1, 'song a' plays and if the value is 2, 'song b' plays etc.
I am using AVFoundation and have created my soundplayer:
var soundPlayer:AVAudioPlayer = AVAudioPlayer()
I have added outlets to my slider:
#IBOutlet weak var audioSlider: UISlider!
#IBOutlet weak var audioSliderPlay: UISlider!
Defined the file location of my audio:
let audioLocation = NSBundle.mainBundle().pathForResource("song a", ofType: ".mp3")
Created a do method for my player:
do {
audioSoundPlayer = try AVAudioPlayer (contentsOfURL: NSURL (fileURLWithPath: audioLocation!))
catch {
print(error)
}
And created my action:
#IBAction func audioSlider(sender: UISlider) {
if (!sender.selected) {
audioSoundPlayer.play()
audioSoundPlayer.numberOfLoops = -1
}
Now - the slider currently activates song a when any value is chosen. Once the audio has started, it cannot be stopped. I need to add a sender.value and create variables that define the value and choose other songs that I am going to add.
Help please?!
Edit with Amit issue:
Amit - I have changed the maximum value of the slider to 3 for 3 audios.
On adding the following to the IBAction, the slider only plays one audio once it reaches the maximum value:
#IBAction func audioSlider(sender: UISlider) {
let sliderValue = (sender.value)
if sliderValue == 1 {
soundPlayer1.play()
soundPlayer1.numberOfLoops = -1
}
else if sliderValue == 2 {
soundPlayer2.play()
soundPlayer2.numberOfLoops = -1
}
else if sliderValue == 3 {
soundPlayer3.play()
soundPlayer3.numberOfLoops = -1
}
else if sliderValue == 0 {
soundPlayer1.stop()
soundPlayer2.stop()
soundPlayer3.stop()
}
}
You slider's value property as sender.value as typecast it to Int and change the audio according to your need.
You do need to set the minimum and maximum value of the slider before using it.
Set default to 0, minimum to 0 and maximum to your need or number of songs you have.
You can use the "pattern-match" operator ~=:
let sliderValue = Int(slider.value)
if 100 ... 199 ~= sliderValue {
print("play audio 1")
}
else if 200 ... 299 ~= sliderValue {
print("play audio 2")
}
1.Set the Slider's max value as number of audio files present in the bundle.
2.Create an array containing the file name or resource path for all the audio.
Like
slider.maxValue = 5;
let audioPaths = ["1.mp3", "2.mp3", "3.mp3", "4.mp3", "5.mp3"];
#IBAction func audioSlider(sender: UISlider)
{
var index = Int(sender.value);
let audioPath = audioPaths[index];
do
{
audioSoundPlayer = try AVAudioPlayer (contentsOfURL: NSURL (fileURLWithPath: audioLocation!))
catch
{
print(error)
}
}
}

Volume Changing using a slider in swift 2

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)

Resources