Search code examples
iosswiftuicollectionviewuicollectionviewcellyoutubeplayer

YouTube player opening unnecessarily during scrolling of CollectionView


I am working on a chatbot where the different type of response comes from the server and I display the response using UICollectionView cells in chat screen. Different type of cells presents according to server response. when server response with playing video, I am presenting the cell that contains youtube player. I am using https://github.com/kieuquangloc147/YouTubePlayer-Swift. The issue is when I scroll chat screen (collectionView) youtube player is opening again and again. Sometimes it is blocking all the UI element and stop scrolling. I tried different methods but can't able to resolve it. Here is the code: PlayerView:

import UIKit

class PlayerView: UIView, YouTubePlayerDelegate {

    override init(frame: CGRect) {
        super.init(frame: frame)
        addYotubePlayer()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // youtube player
    lazy var youtubePlayer: YouTubePlayerView = {
        let viewFrame = UIScreen.main.bounds
        let player = YouTubePlayerView(frame: CGRect(x: 0, y: 0, width: viewFrame.width - 16, height: viewFrame.height * 1/3))
        player.delegate = self
        return player
    }()

    // used as an overlay to dismiss the youtube player
    let blackView = UIView()

    // youtube player loader
    lazy var playerIndicator: UIActivityIndicatorView = {
        let indicator = UIActivityIndicatorView()
        indicator.activityIndicatorViewStyle = .whiteLarge
        indicator.hidesWhenStopped = true
        return indicator
    }()

    // shows youtube player
    func addYotubePlayer() {
        if let window = UIApplication.shared.keyWindow {
            blackView.frame = window.frame
            self.addSubview(blackView)
            blackView.backgroundColor = UIColor(white: 0, alpha: 0.5)
            let tap = UITapGestureRecognizer(target: self, action: #selector(handleDismiss))
            tap.numberOfTapsRequired = 1
            tap.cancelsTouchesInView = false
            blackView.addGestureRecognizer(tap)


            let centerX = UIScreen.main.bounds.size.width / 2
            let centerY = UIScreen.main.bounds.size.height / 2

            blackView.addSubview(playerIndicator)
            playerIndicator.center = CGPoint(x: centerX, y: centerY)
            playerIndicator.startAnimating()

            blackView.addSubview(youtubePlayer)
            youtubePlayer.center = CGPoint(x: centerX, y: centerY)

            blackView.alpha = 0
            youtubePlayer.alpha = 0

            UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
                self.blackView.alpha = 1
                self.youtubePlayer.alpha = 1
            }, completion: nil)
        }
    }

    func play(_ videoID: String) {
        youtubePlayer.loadVideoID(videoID)
    }

    @objc func handleDismiss() {
        blackView.removeFromSuperview()
        UIApplication.shared.keyWindow?.viewWithTag(24)?.removeFromSuperview()
        UIApplication.shared.keyWindow?.removeFromSuperview()
    }

    func playerReady(_ videoPlayer: YouTubePlayerView) {
        self.playerIndicator.stopAnimating()
    }

    func playerStateChanged(_ videoPlayer: YouTubePlayerView, playerState: YouTubePlayerState) {
    }

    func playerQualityChanged(_ videoPlayer: YouTubePlayerView, playbackQuality: YouTubePlaybackQuality) {
    }

}

YouTubePlayerCell (Which I present in collectionView wthe hen server responds for video):

import UIKit

class YouTubePlayerCell: ChatMessageCell {

    var player: PlayerView = PlayerView(frame: UIScreen.main.bounds)

    override func setupViews() {
        super.setupViews()
        setupCell()
    }

    func setupCell() {
        messageTextView.frame = CGRect.zero
        textBubbleView.frame = CGRect.zero
    }

    func loadVideo(with videoID: String) {
        player.tag = 24
        UIApplication.shared.keyWindow?.addSubview(player)
        player.play(videoID)
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        player.removeFromSuperview()
        UIApplication.shared.keyWindow?.viewWithTag(24)?.removeFromSuperview()
    }

}

Here is how I am presenting the YouTubePlayerCell in cellForItemAt method of UICollectionView

let message = messages[indexPath.row]
    if message.actionType == ActionType.video_play.rawValue {
                if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ControllerConstants.youtubePlayerCell, for: indexPath) as? YouTubePlayerCell {
                    self.resignResponders()
                    if let videoId = message.videoData?.identifier {
                        cell.loadVideo(with: videoId)
                    }
                    return cell
                }
            }

Full Source Code can be found here: https://github.com/imjog/susi_iOS/tree/ytplayer


Solution

  • Thanks for your answers. I solve this by this way:

    Rather than presenting Player on setting on the cell, I am now adding a thumbnail to the cell and a button on thumbnail view so that whenever the user clicks play button, it opens a new controller (Previously I was presenting in UIWindow) and presenting it as modalPresentationStyle of overFullScreen by using protocol because cell cannot present a ViewController.

    Protocol: (In YouTubePlayerCell class)

    protocol PresentControllerDelegate: class {
        func loadNewScreen(controller: UIViewController) -> Void
    }
    

    Final YouTubePlayer.swift:

    import UIKit
    import Kingfisher
    
    protocol PresentControllerDelegate: class {
        func loadNewScreen(controller: UIViewController) -> Void
    }
    
    class YouTubePlayerCell: ChatMessageCell {
    
        weak var delegate: PresentControllerDelegate?
    
        var message: Message? {
            didSet {
                addThumbnail()
            }
        }
    
        lazy var thumbnailView: UIImageView = {
            let imageView = UIImageView()
            imageView.image = ControllerConstants.Images.placeholder
            imageView.contentMode = .scaleAspectFill
            imageView.clipsToBounds = true
            imageView.layer.cornerRadius = 15
            imageView.isUserInteractionEnabled = true
            return imageView
        }()
    
        lazy var playButton: UIButton = {
            let button = UIButton(type: .system)
            button.setImage(ControllerConstants.Images.youtubePlayButton, for: .normal)
            button.addTarget(self, action: #selector(playVideo), for: .touchUpInside)
            button.translatesAutoresizingMaskIntoConstraints = false
            return button
        }()
    
        override func setupViews() {
            super.setupViews()
            setupCell()
            prepareForReuse()
        }
    
        func setupCell() {
            messageTextView.frame = CGRect.zero
            textBubbleView.frame = CGRect(x: 8, y: 0, width: 208, height: 158)
            textBubbleView.layer.borderWidth = 0.2
            textBubbleView.backgroundColor = .white
        }
    
        override func prepareForReuse() {
            super.prepareForReuse()
            thumbnailView.image = nil
        }
    
        func addThumbnail() {
            textBubbleView.addSubview(thumbnailView)
            textBubbleView.addConstraintsWithFormat(format: "H:|-4-[v0]-4-|", views: thumbnailView)
            textBubbleView.addConstraintsWithFormat(format: "V:|-4-[v0]-4-|", views: thumbnailView)
            self.downloadThumbnail()
            self.addPlayButton()
        }
    
        func addPlayButton() {
            thumbnailView.addSubview(playButton)
            playButton.heightAnchor.constraint(equalToConstant: 44).isActive = true
            playButton.widthAnchor.constraint(equalToConstant: 44).isActive = true
            playButton.centerXAnchor.constraint(equalTo: thumbnailView.centerXAnchor).isActive = true
            playButton.centerYAnchor.constraint(equalTo: thumbnailView.centerYAnchor).isActive = true
        }
    
        func downloadThumbnail() {
            if let videoID = message?.videoData?.identifier {
                let thumbnailURLString = "https://img.youtube.com/vi/\(videoID)/default.jpg"
                let thumbnailURL = URL(string: thumbnailURLString)
                thumbnailView.kf.setImage(with: thumbnailURL, placeholder: ControllerConstants.Images.placeholder, options: nil, progressBlock: nil, completionHandler: nil)
            }
        }
    
        @objc func playVideo() {
            if let videoID = message?.videoData?.identifier {
                let playerVC = PlayerViewController(videoID: videoID)
                playerVC.modalPresentationStyle = .overFullScreen
                delegate?.loadNewScreen(controller: playerVC)
            }
        }
    
    }
    

    Delegate implementation in CollectionViewController:

    extension ChatViewController: PresentControllerDelegate {
    
        func loadNewScreen(controller: UIViewController) {
            self.present(controller, animated: true, completion: nil)
        }
    
    }
    

    Final source code can be found here: https://github.com/fossasia/susi_iOS/pull/372