YouTube player opening unnecessarily during scrolling of CollectionView
Asked Answered
P

3

7

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

Peevish answered 13/7, 2018 at 5:4 Comment(0)
P
0

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

Peevish answered 21/7, 2018 at 6:49 Comment(0)
B
1

I can see that in the below code

if let videoId = message.videoData?.identifier {
                        cell.loadVideo(with: videoId)
                    }

you are calling loadVideo method, which is responsible for showing the player. So while scrolling you are reusing the cell and it calls loadVideo method and present the player. so the solution is don't start playing the video by default on displaying the cell, provide a play/pause button on the cell video overlay and on clicking the the button start playing the video. If my analysis is wrong please let me know, what exact issue you have.

Backus answered 16/7, 2018 at 6:21 Comment(3)
This solution I was thinking as optional. But the thing is why current method is not working when I am removing the subviews both when video player stops or in prepareForReuse() methodPeevish
see its working but the thing is that after executing prepareForReuse() it again opens the player view because of loadVideo() method, you can check this using print() log statements.Backus
Thank you @Backus I finally able to resolve the issue. I used a UIViewController for player and presented overFullScreen rather that calling UIView (PlayerView). I will post correct answer here too. Also you can check about github linkPeevish
T
0

Why do you add the player as a subView each time you have to play the video ? My suggestion would be, as you are adding the player view on the whole screen, you can have just one instance of the view and add it just once(may be at the beginning) and keep it hidden. To play the video just unhide the player and load the video.

Instead best practice would be to have a View controller for Youtube Player and present it with the video id each time you need to play and then dismissing when done.

Transpierce answered 20/7, 2018 at 6:16 Comment(0)
P
0

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

Peevish answered 21/7, 2018 at 6:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.