Swift - AVPlayer - Stop player in another cell when a new player starts

I am working on a podcast application and achieved all the streaming-related parts of the application. The problem explained below, how can I achieve this?

Current:
The player view is hidden by default. When the user presses the "Play" button, the player view unhides and the player starts streaming. But it won't stop streaming if the user presses another "Play" button so both streams play simultaneously.

Goal:
When the user starts playing a new podcast by pressing another Play button, I want other players would stop playing and disappear.

Extra:
The whole podcast view awakes from the xib cell. So, the problem is, how can I manipulate another cell view's member when pressing a button in another cell view
You should show code, that would be much easier to explain.

 I want other players

You want a single player to play at any time ?
So, you should have a reference to the activePlayer declared at the class level.
It is nil to begin with.

When you Play, you should first stop the activePlayer (after testing it is not nil)
Then dismiss and set to nil
Then create a new player instance and set it to activePlayer.

If you have a stop func, do the same:
stop the active player, dismiss it and set to nil.

Could you better explain your Extra question ?

@Claude31 my problem here is I have declared my player in ViewCell, not in ViewContoller. So every cell has its player currently, thus I can't reach another cell's player when a player starts.
Why did you do so ? You could have just the button for the player, but have the active player at view level.

However, you could do this:
when you tap on play in a cell, explore all the cells and stop their player if not nil.
That's not a very good design, but that could work.
Thank you for all of your efforts!

This was my first attempt to work with xibs and it went a little bit messy. I'd like to hear advice for view level player way too.

For now, I will try the second way. How can I achieve exploring all the cells available?
Iin the play button action of the cell:
get the tableView with cell.superview.
tableView.visibleCells,
gives you all visible cells in an array [UITableViewCell]

then loop through this array and test their player to stop if needed.

I cannot work with cell.superview.tableView.visibleCells in play button action which is in ViewCell file, it gives cannot find in scope error.
It is not
cell.superview.tableView.visibleCells

But
let tableView = cell.superview
then use
tableView.visibleCells

You could have a safety check:
Code Block
if let tableView = cell.superview as? UITableView {
// then use
tableView.visibleCells
}

I still get the cannot find in scope error for "cell". I tried to declare tableView in and out of play button action but no chance. My cells awake in CollectionView, not in TableView.

I have found that this way I can get the visible cells:

Code Block
let visibleIndexPaths = collectionView.indexPathsForVisibleItems


But I don't know what to do with it..
You didn't tell it was a collectionView.

So change with:

Code Block
if let collectionView = cell.superview as? UICollectionView {
let visibleCells = collectionView.visibleCells
for cell in visibleCells where cell is CollectionViewCell { // The type of your cells
// now you can access to properties
if cell.player != nil { // I suppose you named the podPlayer as player
// stop it playing
// dismiss
// set to nil
}
}
}


The problem is still here, I get the same error: "Can't find 'cell' in scope." when I use the code. For more information, here is the file I'm working on:

PodcastCellView: (Where I declare the player, player view's button and button action. But CollectionView is declared in ViewController.)

Code Block swift


I get the same error even I use the code in PodcastViewController.
Sure. cell is not known.

replace line 2 with:
Code Block
if let collectionView = self.superview as? UICollectionView {

After replacing the cell with self in line 2, I get another error on line 5.

Value of type 'UICollectionViewCell' has no member 'player'

If I replace that cell. with self., code compiles successfully. But nothing changes on the application side. When I click another play button while a stream playing, the second stream starts playing.
Could you show your code (complete for PodcastViewCell, the UICollectionViewCell). I have been guessing some missing parts, it is not very efficient.
How did you name the player there ?

Sure it compiles with self instead of cell, but it misses the point which is to stop players from any other cell, not the current one (self).
My player declared as player in PodcastViewCell

Code Block swift
var player: AVPlayer?



This is the complete code for PodcastViewCell

Code Block swift
import UIKit
import AVKit
import AVFoundation
class PodcastViewCell: UICollectionViewCell {
  // UILabels
  @IBOutlet weak var categoryLabel: UILabel!
  @IBOutlet weak var titleLabel: UILabel!
  @IBOutlet weak var dateLabel: UILabel!
  @IBOutlet weak var podcastUnavailableLbl: UILabel!
   
  // Constraints
  @IBOutlet weak var linehg: NSLayoutConstraint!
  @IBOutlet weak var categoryLabelHeight: NSLayoutConstraint!
  @IBOutlet weak var podcastBtnWd: NSLayoutConstraint!
   
  // UISliders
  @IBOutlet weak var slider: UISlider!
   
  // UIViews
  @IBOutlet weak var podcastView: UIView!
   
  // UIButtons
  @IBOutlet weak var playPauseBtn: UIButton!
  @IBOutlet weak var podcastBtn: UIButton!
  var player: AVPlayer?
  var playerItem: AVPlayerItem?
  var timeObserverToken: Any?
   
  var urlString: String?
  var podcastLink: String?
   
  var played = false
   
   
  override func awakeFromNib() {
    super.awakeFromNib()
    podcastViewVisibility()
    slider.value = 0
  }
   
  var item: News? {
    didSet {
      if let news = item {
        titleLabel.text = news.title ?? ""
        categoryLabel.text = news.category?.title ?? ""
        dateLabel.text = news.publishDate ?? ""
      }
    }
  }
  func prepareCell(news: News?) {
    item = news
  }
   
  func prepareCellAuthor(author: AuthorSubList?) {
    itemAuthor = author
  }
   
  var itemAuthor: AuthorSubList? {
    didSet {
      if let column = itemAuthor {
        titleLabel.text = column.title ?? ""
        categoryLabel.text = ""
        categoryLabelHeight.constant = 0
        dateLabel.text = column.publishDate ?? ""
      }
    }
  }
   
  @IBAction func selectItem(_ sender: Any) {
    if let news = item {
      if let id = news.itemId {
        delegate?.selectItemAction(itemId: id)
        delegate?.selectPopularNews(news: news, index: tag)
      }
    }
       
    else if let author = itemAuthor {
      if let id = author.itemId {
        delegate?.selectItemAction(itemId: id)
      }
    }
  }
  @IBAction func playPodcast(_ sender: Any) {
     
    if let collectionView = self.superview as? UICollectionView {
     let visibleCells = collectionView.visibleCells
     for cell in visibleCells where cell is PodcastViewCell {
if cell.player != nil {         }
     }
    }
    if played == false {
      NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidReachEnd), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
       
      if let itemOffset = allItems?.itemListv2.firstIndex(where: {$0.itemId == itemAuthor?.itemId}) {
        podcastLink = allItems?.itemListv2[itemOffset].podcastsound
      }
      let url = podcastLink ?? " "
       
      if url != "" {
        let playerItem = AVPlayerItem( url:NSURL( string:url )! as URL )
        player = AVPlayer(playerItem:playerItem)
        player!.rate = 1.0;
         
        podcastView.isHidden = false
         
        player!.play()
        playPauseBtn.setImage(UIImage(systemName: "pause.fill"), for: .normal)
         
        player?.addProgressObserver { progress in
          self.slider.setValue(Float(progress), animated: true)
        }
        played = true
      } else {
        podcastUnavailableLbl.isHidden = false
      }
    } else {
      podcastView.isHidden = true
player?.replaceCurrentItem(with: nil)
      slider.value = 0
      played = false
    }
  }
  deinit {
    NotificationCenter.default.removeObserver(self)
  }
   
  @IBAction func playPause(_ sender: Any) {
    if player?.timeControlStatus == .playing {
      player?.pause()
      playPauseBtn.setImage(UIImage(systemName: "play.fill"), for: .normal)
    } else {
      player?.play()
playPauseBtn.setImage(UIImage(systemName: "pause.fill"), for: .normal)
    }
  }
}
extension AVPlayer {
  func addProgressObserver(action:@escaping ((Double) -> Void)) -> Any {
    return self.addPeriodicTimeObserver(forInterval: CMTime.init(value: 1, timescale: 1), queue: .main, using: { time in
      if let duration = self.currentItem?.duration {
        let duration = CMTimeGetSeconds(duration), time = CMTimeGetSeconds(time)
        let progress = (time/duration)
        action(progress)
      }
    })
  }
}
                                                     


Do you still get the error on line 93 ?

If so, try the following:

Code Block
@IBAction func playPodcast(_ sender: Any) {
if let collectionView = self.superview as? UICollectionView {
let visibleCells = collectionView.visibleCells
for cell in visibleCells {
if let podCell = cell as? PodcastViewCell {
print("cell is PodcastViewCell")
if podCell.player != nil {
print("podCell.player found")
} else {
print("podCell.player NOT found")
}
} else {
print("cell is NOT PodcastViewCell")
}
}
}

And tell what you get exactly on console.

Or simply change:

Code Block
if let collectionView = self.superview as? UICollectionView {
let visibleCells = collectionView.visibleCells
for cell in visibleCells where cell is PodcastViewCell {
if (cell as! PodcastViewCell).player != nil { }
}


The error on line 93 gone and I compiled the code successfully. This is the output:

Code Block debugger
cell is PodcastViewCell
podCell.player NOT found
cell is PodcastViewCell
podCell.player NOT found
cell is PodcastViewCell
podCell.player NOT found
cell is PodcastViewCell
podCell.player found
cell is PodcastViewCell
podCell.player NOT found
cell is PodcastViewCell
podCell.player NOT found
cell is PodcastViewCell
podCell.player NOT found
cell is PodcastViewCell
podCell.player NOT found


Stop when another start function still not working for information.
It is normal at first pass, because no player yet defined.

Could you add another print:
Code Block
if url != "" {
let playerItem = AVPlayerItem( url:NSURL( string:url )! as URL )
print("New player for", url)
player = AVPlayer(playerItem:playerItem)
player!.rate = 1.0;


This is the output with the latest print:

First click (to show/play)

Code Block debugger
cell is PodcastViewCell
podCell.player NOT found
cell is PodcastViewCell
podCell.player found
cell is PodcastViewCell
podCell.player NOT found
cell is NOT PodcastViewCell
cell is PodcastViewCell
podCell.player found
cell is PodcastViewCell
podCell.player NOT found
New player for https://podcast.link


Second click on same button (to hide/stop):

Code Block debugger
cell is PodcastViewCell
podCell.player NOT found
cell is PodcastViewCell
podCell.player found
cell is PodcastViewCell
podCell.player NOT found
cell is NOT PodcastViewCell
cell is PodcastViewCell
podCell.player found
cell is PodcastViewCell
podCell.player NOT found

OK, I missed it, you have effectively some cell with active player.

It is surprising in the first run that you have 2 found, as none has yet been created

What are exactly the cells you hit ?

Could you add yet another print:
Code Block
@IBAction func playPodcast(_ sender: Any) {
print("Title", self.title)
if let collectionView = self.superview as? UICollectionView {
let visibleCells = collectionView.visibleCells


There is also a case
cell is NOT PodcastViewCell
What cell is this ?

Sorry for the late reply. How are you lately? I hope everything going great!

I tried to add the latest print but got an error.

Code Block swift
print("Title", self.title) <----------------- "Value of type 'PodcastViewCell' has no member 'title'"



It is surprising in the first run that you have 2 found, as none has yet been created

I pressed different play buttons while other players are active, so this should why there are "player found" outputs shown.


There is also a case cell is NOT PodcastViewCell What cell is this ?

I rerun the code with prints and didn't get any NOT values. I will debug and notify you when I get NOT values. This is the output I got:

Code Block debugger
cell is PodcastViewCell
podCell.player NOT found
cell is PodcastViewCell
podCell.player NOT found
cell is PodcastViewCell
podCell.player NOT found
cell is PodcastViewCell
podCell.player NOT found
cell is PodcastViewCell
podCell.player NOT found
cell is PodcastViewCell
podCell.player NOT found
cell is PodcastViewCell
podCell.player NOT found
cell is PodcastViewCell
podCell.player NOT found
cell is PodcastViewCell
podCell.player NOT found


I did not read your code with enough attention.
It is not title but titleLabel.
Thank you for all of your efforts but I am really giving up on this idea. Instead, I'll try to accomplish it in another way. When the user clicks the "play button", I'll show "podcast view" with a blurry background and a player. When the user wants to close, I'll stop the player and then hide the view. I am now looking into how can I create on PodcastViewCell file but show a full-screen view out of the fixed-size cell.

I am open to advice but I'd totally understand if you say "meh that's all from me" 😇
To go further would require to get the whole code to undertsand what happens exactly by running code.

So, continue with your other idea (I responded to the thread).

And don't forget to close this thread by marking the correct (or most useful) answer.

Try both at same time:

               "yourAVPlayer".replaceCurrentItem(with: nil)
              "theURLoftheAVPlayer".removeAll()
Swift - AVPlayer - Stop player in another cell when a new player starts
 
 
Q