How to stop AVAudioPlayer from colliding with other sound from another viewcontroller? - swift

I have two view controllers for this app. The first one includes a button that can be clicked for a video to play. The second one has a list of songs which can be played. My problem is when I play a song from the 2nd controller, the background music collides with the sound of the video. I want the music from the 2nd controller to stop and not to collide with the sound of the video from the 1st controller.
I tried to use this code in the 1st controller but it didn't work:
audioPlayer.stop()
It gives an error saying that it is "EXC_BAD_ACCESS Swift"
Could anyone please help me? Thx :)

As per my understanding you need to manage A single player throughout your app. In my one project I managed to use it in following way. Please check if that helps you
struct Manager
{
//Required Objects - AVFoundation
///AVAudio Session
static var recordingSession: AVAudioSession!
///AVAudio Recorder
static var recorder: AVAudioRecorder?
///AVAudio Player
static var player: AVAudioPlayer?
///Bool to check an audio is being played or not
static var recordingalreadyPlayedStatus = Bool()
}
Select File From MainSource and Play it
//MARK: Button Handler
/**
Function is used to play selected Row music Audio File
- parameter sender : UIButton Reference
*/
#objc func playButtonHandler(sender: UIButton)
{
let buttonPosition : CGPoint = sender.convert(CGPoint.zero, to: self.recordingListTableView)
let indexPath : IndexPath = self.recordingListTableView.indexPathForRow(at: buttonPosition)!
///Check is Recorder is Recording Sound ?
if (Manager.recorder != nil) {
return
}
///Check Audio Already Playing ?
/// Bool Used to check is anything getting Played
if (Manager.recordingalreadyPlayedStatus) {
///If yes
///Stop From Playing
if self.currentIndexPath == indexPath {
///Time to stop playing music
var cell = self.recordingListTableView.dequeueReusableCell(withIdentifier: "Cell") as! recordingListCell
cell = self.recordingListTableView.cellForRow(at: self.currentIndexPath) as! recordingListCell
cell.playButton.setImage(#imageLiteral(resourceName: "start"), for: .normal)
Manager.player?.stop()
Manager.recordingalreadyPlayedStatus = false
self.currentIndexPath = indexPath
print("Stopped Playing Current Sound")
}
else {
///Time to stop playing music
var cell = self.recordingListTableView.dequeueReusableCell(withIdentifier: "Cell") as! recordingListCell
cell = self.recordingListTableView.cellForRow(at: self.currentIndexPath) as! recordingListCell
cell.playButton.setImage(#imageLiteral(resourceName: "start"), for: .normal)
Manager.player?.stop()
Manager.recordingalreadyPlayedStatus = false
///Play music at new index
self.playMusic(indexPath: indexPath)
}
}
else {
/// NoThing was Played lets play Directly
self.playMusic(indexPath: indexPath)
}
print("Starting to Play Sound")
}
Play Audio
//MARK: Play Music
/**
This Function get the URL of Audio and Plays it in View
*/
func playMusic(indexPath: IndexPath)
{
//get cell Index Status Using CGPoint
var cell = self.recordingListTableView.dequeueReusableCell(withIdentifier: "Cell") as! recordingListCell
cell = self.recordingListTableView.cellForRow(at: indexPath) as! recordingListCell
//Set Current value so we can change status if any other audio being played
self.currentIndexPath = indexPath
coreClassObject.initWithData(data: fileNameArray, index: indexPath.row)
//Play music now
let isMusicPlayed = RecordingManager.shared.playRecording(fileName: coreClassObject.audioFileName)
//Change Button status
if (isMusicPlayed)
{
//yes being played
//time to change button Text
cell.playButton.setImage(#imageLiteral(resourceName: "stop"), for: .normal)
}
else
{
//music is not played
cell.playButton.setImage(#imageLiteral(resourceName: "start"), for: .normal)
}
}
Function used to check is recording in progress if no, play Audio
//MARK: Play Recording at selected Index
/**
This function is used to check is any file being currently Played ?
if no, Play File else return bool that another file is being already played
- parameter fileName : File Name refer to Audio File name which is to be played
- returns : Bool That file is Successfully played or not
*/
func playRecording(fileName:String) -> Bool {
/// Just a bool used here to check is recording being played
/// Used to Update timer in TableView Cell
var recordingPlayed = Bool()
/// Whenever we need to play an audio file we will allocate player
/// Optional will let you compare that player is nil ?
if Manager.recorder == nil {
/// Player is found nil
/// Thus no Audio is being Played here
/// Yes, We can Play Sound
recordingPlayed = true
Manager.recordingalreadyPlayedStatus = true
//Set player with audio File
do {
try Manager.player = AVAudioPlayer.init(contentsOf: returnPathAtSelectedIndex(fileName: fileName))
//Set required delegates and Values
Manager.player?.delegate = self
Manager.player?.volume = 1.0
Manager.player?.prepareToPlay()
Manager.player?.play()
}
catch {
print("Error while playing music: \(error.localizedDescription)")
}
}
else {
/// Player is already playing a file
/// Can not play audio for now
recordingPlayed = false
Manager.recorder = nil
Manager.recordingalreadyPlayedStatus = false
}
return recordingPlayed
}

Related

How to set NowPlaying properties with a AVQueuePlayer in Swift?

I have an AVQueuePlayer that gets songs from a Firebase Storage via their URL and plays them in sequence.
static func playQueue() {
for song in songs {
guard let url = song.url else { return }
lofiSongs.append(AVPlayerItem(url: url))
}
if queuePlayer == nil {
queuePlayer = AVQueuePlayer(items: lofiSongs)
} else {
queuePlayer?.removeAllItems()
lofiSongs.forEach { queuePlayer?.insert($0, after: nil) }
}
queuePlayer?.seek(to: .zero) // In case we added items back in
queuePlayer?.play()
}
And this works great.
I can also make the lock screen controls appear and use the play pause button like this:
private static func setRemoteControlActions() {
let commandCenter = MPRemoteCommandCenter.shared()
// Add handler for Play Command
commandCenter.playCommand.addTarget { [self] event in
queuePlayer?.play()
return .success
}
// Add handler for Pause Command
commandCenter.pauseCommand.addTarget { [self] event in
if queuePlayer?.rate == 1.0 {
queuePlayer?.pause()
return .success
}
return .commandFailed
}
}
The problem comes with setting the metadata of the player (name, image, etc).
I know it can be done once by setting MPMediaItemPropertyTitle and MPMediaItemArtwork, but how would I change it when the next track loads?
I'm not sure if my approach works for AVQueueplayer, but for playing live streams with AVPlayer you can "listen" to metadata receiving.
extension ViewController: AVPlayerItemMetadataOutputPushDelegate {
func metadataOutput(_ output: AVPlayerItemMetadataOutput, didOutputTimedMetadataGroups groups: [AVTimedMetadataGroup], from track: AVPlayerItemTrack?) {
//look for metadata in groups
}
}
I added the AVPlayerItemMetadataOutputPushDelegate via an extension to my ViewController.
I also found this post.
I hope this gives you a lead to a solution. As said I'm not sure how this works with AVQueuePlayer.

MPRemoteCommandCenter play/pause Flashing when touched

i'm working on music player app that written in Swift, audio streams with AVPlayer and everything is fine
but when i tried to add MPRemoteCommandCenter to my app there was a lot of bug that i dont know even why this happend
link to video that describes my problem
AVPlayer Implemented like:
func setupPlayer() {
let item = AVPlayerItem(url: musicURL)
self.player = AVPlayer.init(playerItem: item)
self.player.play()
self.player.volume = 1
self.player.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, preferredTimescale: 1), queue: DispatchQueue.main, using: { (time) in
if self.player.currentItem?.status == .readyToPlay {
self.reloadNowPlayingInfo()
let currentTime = self.player.currentTime().seconds
self.playingTime.text = currentTime.getTimeString()
self.playerSlider.value = currentTime/duration
}
})
}
func reloadNowPlayingInfo() {
var info = [String : Any]()
info[MPMediaItemPropertyTitle] = self.titleText
info[MPMediaItemPropertyArtwork] = MPMediaItemArtwork.init("some image")
info[MPMediaItemPropertyPlaybackDuration] = seconds
info[MPNowPlayingInfoPropertyElapsedPlaybackTime] = currentSecs
info[MPMediaItemPropertyArtist] = "Artist name"
MPNowPlayingInfoCenter.default().nowPlayingInfo = info
}
and for command center,
MPRemoteCommandCenter Implemented like:
func setupCommandCenter() {
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.playCommand.isEnabled = true
commandCenter.pauseCommand.isEnabled = true
commandCenter.playCommand.addTarget(self, action: #selector(self.playCommand(_:)))
commandCenter.pauseCommand.addTarget(self, action: #selector(self.pauseCommand(_:)))
}
#objc func playCenter(_ action: MPRemoteCommandEvent) {
self.state = .play
self.playBtn.setBackgroundImage("some image"), for: .normal)
self.player.play()
self.fetchTracks()
}
#objc func pauseCenter(_ action: MPRemoteCommandEvent) {
self.state = .pause
self.playBtn.setBackgroundImage("some image"), for: .normal)
self.player.pause()
self.fetchTracks()
}
In addition to the code you've presented you might also have called the following somewhere in your app delegate:
UIApplication.shared.beginReceivingRemoteControlEvents()
Doing this in addition to using MPRemoteCommandCenter.shared() seems to cause a race condition.
According to Apple's documentation:
In iOS 7.1 and later, use the shared MPRemoteCommandCenter object to register for remote control events. You do not need to call this method when using the shared command center object.
This method starts the delivery of remote control events using the responder chain.
Remove that method from your app delegate and you should be fine.
If you add 2 observer to your code to receive player notifications. you may see lag or jump in player lock screen.
avoid adding observer and once a target.
commandCenter.playCommand.addTarget(self, action: #selector(self.playCommand(_:)))
only once. and remove it when u dont wanted

AVPlayer cannot play video

I am trying to play a video inside a subclass of JSQMessagesViewController, but it doesn't start playing onClick event.
I am downloading the video from firebase, and then I write the data on the device by appending a unique reference -LK7WSGtGAQ2anpDxkCQ. Please see below print out statement for the complete path.
//response to collection view tap events
//called every time we type on a message
override func collectionView(_ collectionView: JSQMessagesCollectionView!, didTapMessageBubbleAt indexPath: IndexPath!) {
let message = messages[indexPath.item]
print("message type is \(message.type) and message media uid is \(message.mediaMessage?.mediaUID)")
if message.type == MessageType.image {
let jsqMessage = jsqMessages[indexPath.item]
let mediaItem = jsqMessage.media as! JSQPhotoMediaItem
let photos = IDMPhoto.photos(withImages: [mediaItem.image])
let browser = IDMPhotoBrowser(photos: photos)
self.present(browser!, animated: true, completion: nil)
}
if message.type == MessageType.video {
let jsqMessage = jsqMessages[indexPath.item]
if jsqMessage.isMediaMessage {
if let mediaItem = jsqMessage.media as? JSQVideoMediaItem {
print("mediaItem.fileURL is \(mediaItem.fileURL)")
let player = AVPlayer(url: mediaItem.fileURL)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
self.present(playerViewController, animated: true) {
playerViewController.player!.play()
}
}
}
}
}
}//end of extension
mediaItem.fileURL is
Optional(file:///Users/bogdanbarbulescu/Library/Developer/CoreSimulator/Devices/7FB42206-997D-4AC2-B0BD-CEE2E22DAFBE/data/Containers/Data/Application/8F17DF31-16B1-4B27-ACB2-015AA55D8979/Documents/-LK7WSGtGAQ2anpDxkCQ)
Update
The video can by played now after appending .MOV to the url above, but there is no sound when it's played. How to fix this?
The problem was that when I was saving the data on the disk, I did not append .MOV extension to the end of the path, thus AVPlayer, could not play the video.
Before:
mediaItem.fileURL is file:///Users/bogdanbarbulescu/Library/Developer/CoreSimulator/Devices/7FB42206-997D-4AC2-B0BD-CEE2E22DAFBE/data/Containers/Data/Application/8F17DF31-16B1-4B27-ACB2-015AA55D8979/Documents/-LK7WSGtGAQ2anpDxkCQ
After: file:///Users/bogdanbarbulescu/Library/Developer/CoreSimulator/Devices/7FB42206-997D-4AC2-B0BD-CEE2E22DAFBE/data/Containers/Data/Application/8F17DF31-16B1-4B27-ACB2-015AA55D8979/Documents/-LK7WSGtGAQ2anpDxkCQ.MOV

avplayer with slow-internet connection not working after first frame

my problem is that when having very bad connection,the activtiy indicator start animating till first frame in video is shown,then disappear thinking is the video is playing,but the video stops playing stuck on loaded first frame,until the whole video is loaded then its resume playing,how to show activity indicator while video is stuck on frame and buffering ,then play until next loaded frame ?
notes:
it's working when internet connetion is off ,video is played until the loaded frame and activity indicator is shown,then when turn on video us resumed to play and activity indicator is hidden
it's working when normal internet connection is present
removing and showing indicator using override observevalue for key path
"currentItem.loadedTimeRanges"/"currentItem.playbackBufferEmpty"
i made a uiview class with avplayer in it
import UIKit
import AVKit
import AVFoundation
class videoplaying: UIView {
override static var layerClass: AnyClass {
return AVPlayerLayer.self;
}
var playerlayer: AVPlayerLayer{
return layer as! AVPlayerLayer;
}
var player: AVPlayer?{
get{
return playerlayer.player
}
set {
playerlayer.player = newValue
}
}
var playetitem: AVPlayerItem?
}
i assigned a uivew in uicollectioncell to this class(using storyboard)
avplayer starts playing and adding observes when pressing play in uicollectioncell
#IBAction func play(_ sender: Any) {
activityindicator.isHidden = false
activityindicator.startAnimating()
self.butttoonheight.isHidden = true
self.postimage.isHidden = true
let url2 = URL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")
let avplayer = AVPlayer(url: url2! )
let playeritem = AVPlayerItem(url: url2!)
//videoss is class of type uiview
videoss.playetitem = playeritem
videoss.playerlayer.player = avplayer
videoss.player?.play()
videoss.player?.addObserver(self, forKeyPath: "currentItem.loadedTimeRanges", options: .new, context: nil)
videoss.player?.addObserver(self, forKeyPath: "rate", options: .new
, context: nil)
videoss.player?.addObserver(self, forKeyPath: "currentItem.playbackBufferEmpty", options: .new, context: nil)
playying.isHidden = false
}
//observing when video is playing
//playpause button to play or pause video while bad network is present video is stuck on first frame and playorpause is not changing while pressed
#IBAction func playorpause(_ sender: Any) {
if videoss.player?.timeControlStatus == AVPlayerTimeControlStatus.paused{
videoss.player?.play()
playying.setImage(UIImage(named: "pas50"), for: .normal)
}
if videoss.player?.timeControlStatus == AVPlayerTimeControlStatus.playing{
videoss.player?.pause()
playying.setImage(UIImage(named: "p24"), for: .normal)
}
}
override public func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "rate"{
print(videoss.player?.rate)
if videoss.player?.rate == 0.0 {
print("dawdaopwdaopwdipo")
}
}
if keyPath == "currentItem.loadedTimeRanges"{
print("its is working")
activityindicator.stopAnimating()
activityindicator.isHidden = true
}
if keyPath == "currentItem.playbackBufferEmpty"
{
activityindicator.startAnimating()
activityindicator.isHidden = false
print("pkawdawdawd")
}
}
I solved this problem by adding a timer that runs ever 0.3 seconds,evrry 0.3seconds it checks the current time of the video if current time equals previous time then the video is not playing activity indicator is shown,if not video if playing activity indicators was hidden,also you need to check if the users pause the video,an advantage is you also get the current time also in the same function.
What i tried also was seeing rate of the video ,but over slow internet the rate was always giving that is playing,but it wasn’t,which didn’t help

AVQueuePlayer scrubber thumbnail always first video

I have implemented player AVPlayerViewController on tvOS using AVQueuePlayer. The playback of videos works as expected but on all videos following the first the scrubber / time slider shows thumbnails for the first video.
The player is being setup using the following code:
class TFLPlayerController: AVPlayerViewController, AVPlayerViewControllerDelegate {
var queuePlayer = AVQueuePlayer()
...
func addVideoToQueue(video: Video, after: AVPlayerItem?){
let mainVideo = AVPlayerItem(URL: NSURL(string: videoURL)!)
self.queuePlayer.insertItem(mainVideo, afterItem: nil)
}
func readyToPlay() {
self.addVideoToQueue(videoRecord, after: nil)
self.player = self.queuePlayer
if let currentItem = queuePlayer.currentItem {
// Set time to get make a call to the API for the next video
let callUpNextAPITime = CMTimeMakeWithSeconds(0.5, currentItem.asset.duration.timescale)
let callUpNextAPITimeValue = NSValue(CMTime: callUpNextAPITime)
let getUpNextObserver = queuePlayer.addBoundaryTimeObserverForTimes([callUpNextAPITimeValue], queue: dispatch_get_main_queue(), usingBlock: { () -> Void in
self.getUpNext()
})
}
self.player?.play()
}
func getUpNext(){
// Get the next video to play
let playlistId = presentingPlaylist?.identifier ?? nil
if let currentItem = self.queuePlayer.currentItem {
// Get the current items id
let currentVideoId = itemVideo[currentItem]?.identifier
if let currentVideoId = currentVideoId {
// Call API to get the next video to be played from the server
dataLayer.getNextVideo(currentVideoId, playlistIdentifier: playlistId) {
video, error in
...
if let nextVideo = video {
let currentVideo: AVPlayerItem = self.queuePlayer.currentItem!
self.addVideoToQueue(nextVideo, after: currentVideo)
}
}
}
}
}
I have also instantiated the AVQueuePlayer using the code below as I thought there might be an issue with the initial queue setup but the issue remain:
AVQueuePlayer(playerItem: mainVideo)