Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Crash on UICollectionViewCell with JWVideoView - Swift

A ViewController has a UICollectionView. One of the cells contains JWVideoView. The app is frequently crashing on prepareForReuse in this cell.

There is no valuable info in the log. So I am having trouble figuring out the reason for the crash.

I've created a project example that demonstrates the crash. You can find it https://github.com/fuxlud/JWExample If the link between the cell and the videoView is removed, the crash will not happen.

import UIKit

class VideoArticleElementCollectionViewCell: UICollectionViewCell {

    // MARK: - Properties

    public var imageURL: String? { didSet { videoView?.imageURL = imageURL } }
    public var videoId: String? { didSet { videoView?.videoId = videoId } }

    @IBOutlet private var videoView: JWVideoView?

    // MARK: - Reuse

    override func prepareForReuse() {
        super.prepareForReuse() // Crashing here! (Thread 1: EXC_BAD_ACCESS (code=1, address=0x7e8))

        videoView?.stopPlayingVideo()
    }

    deinit {

        videoView?.stopPlayingVideo()
    }
}








import UIKit

class JWVideoView: UIView, JWPlayerDelegate {

    // MARK: Properties

    public var imageURL: String?
    public var videoId: String? { didSet { setupPlayer() } }

    private var jwPlayer: JWPlayerController?
    private let jwPlayerURL = "https://content.jwplatform.com/manifests/"
    private var didPause = false

    // MARK: - Initialization

    override init(frame: CGRect) {

        super.init(frame: frame)
        setup()
    }

    convenience init() {

        self.init(frame: CGRect.zero)
    }

    required init?(coder aDecoder: NSCoder) {

        super.init(coder: aDecoder)
        setup()
    }

    // MARK: - Setup

    private func setup() {}

    private func setupPlayer() {



            guard let videoId = self.videoId else { return }

            let playerURL = jwPlayerURL + videoId + ".m3u8"

            let configuration: JWConfig = JWConfig(contentURL: playerURL)
            configuration.controls = true
            configuration.autostart = true
//            configuration.premiumSkin = JWPremiumSkinGlow
            configuration.image = imageURL

            jwPlayer = JWPlayerController(config: configuration)

            if let player = jwPlayer {

                player.forceFullScreenOnLandscape = true
                player.forceLandscapeOnFullScreen = true
                player.view?.autoresizingMask = [.flexibleHeight, .flexibleWidth]
                player.view?.frame = bounds
                player.delegate = self
                player.volume = 0.0
                if let view = player.view { addSubview(view) }
            }

    }

    // MARK: - Orientation

    private func enableAllOrientation(enable: Bool) {

        if let delegate = UIApplication.shared.delegate as? AppDelegate {

//            delegate.shouldEnableLandscape = enable
        }
    }

    // MARK: API

    public func stopPlayingVideo() {

        enableAllOrientation(enable: false)

        if jwPlayer != nil {

            jwPlayer!.stop()
        }
    }

    // MARK: - JWPlayerDelegate

    internal func onFullscreen(_ status: Bool) {

        if status == false {

            let value = UIInterfaceOrientation.portrait.rawValue
            UIDevice.current.setValue(value, forKey: "orientation")
        }
    }

    internal func onPlayAttempt() {

        if jwPlayer != nil {

            enableAllOrientation(enable: true)


        }
    }

    internal func onPlay(_ oldValue: String) {

        if didPause {

            didPause = false
        }
    }

    internal func onPause(_ oldValue: String) {

        didPause = true

    }

    internal func onComplete() {

    }

}
like image 343
Luda Avatar asked Jul 28 '19 07:07

Luda


1 Answers

Based on your example project a saw the following issue inside your JWVideoView class: everytime you setting the videoId property it initiliaze the jwPlayer again, and also readds this view again to the stack.

1. Solution (remove the playerView and set the player to nil):

  private func setupPlayer() {

  jwPlayer?.view?.removeFromSuperview()
  jwPlayer = nil

  guard let videoId = self.videoId else { return }

  let playerURL = jwPlayerURL + videoId + ".m3u8"

  let configuration: JWConfig = JWConfig(contentURL: playerURL)
  configuration.controls = true
  configuration.autostart = true
  configuration.image = imageURL

  jwPlayer = JWPlayerController(config: configuration)
  jwPlayer?.forceFullScreenOnLandscape = true
  jwPlayer?.forceLandscapeOnFullScreen = true
  jwPlayer?.view?.autoresizingMask = [.flexibleHeight, .flexibleWidth]
  jwPlayer?.view?.frame = bounds
  jwPlayer?.delegate = self
  jwPlayer?.volume = 0.0

  if let view = jwPlayer?.view {
      addSubview(view)
  }

}

2. Solution (keep the player and the view instance and reset the configuration of the player)

  private func setupPlayer() {

  guard let videoId = self.videoId else { return }

  let playerURL = jwPlayerURL + videoId + ".m3u8"

  let configuration: JWConfig = JWConfig(contentURL: playerURL)
  configuration.controls = true
  configuration.autostart = true
  configuration.image = imageURL

  if jwPlayer == nil {

      jwPlayer = JWPlayerController(config: configuration)
      jwPlayer?.forceFullScreenOnLandscape = true
      jwPlayer?.forceLandscapeOnFullScreen = true
      jwPlayer?.view?.autoresizingMask = [.flexibleHeight, .flexibleWidth]
      jwPlayer?.view?.frame = bounds
      jwPlayer?.delegate = self
      jwPlayer?.volume = 0.0

      if let view = jwPlayer?.view {
          addSubview(view)
      }
  }else{
    //reset the configuration of the player here. but i dont now how this is possible with jwPlayer
  }

}

like image 159
Klinki Avatar answered Oct 25 '22 22:10

Klinki