使用 JWVideoView 在 UICollectionViewCell 上崩溃 - Swift
Crash on UICollectionViewCell with JWVideoView - Swift
一个ViewController
有一个UICollectionView
。其中一个单元格包含 JWVideoView
。该应用经常在此单元格中的 prepareForReuse
崩溃。
日志中没有有价值的信息。所以我无法找出崩溃的原因。
我创建了一个演示崩溃的项目示例。你可以找到它https://github.com/fuxlud/JWExample
如果去掉cell
和videoView
之间的link,就不会发生崩溃。
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() {
}
}
根据您的示例项目,在您的 JWVideoView class 中发现了以下问题:每次您设置 videoId 属性 时,它都会再次启动 jwPlayer,并再次将该视图读取到堆栈中。
1.解决方案(去掉playerView,设置player为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。解决方案(保留播放器和视图实例并重置播放器的配置)
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
}
}
一个ViewController
有一个UICollectionView
。其中一个单元格包含 JWVideoView
。该应用经常在此单元格中的 prepareForReuse
崩溃。
日志中没有有价值的信息。所以我无法找出崩溃的原因。
我创建了一个演示崩溃的项目示例。你可以找到它https://github.com/fuxlud/JWExample
如果去掉cell
和videoView
之间的link,就不会发生崩溃。
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() {
}
}
根据您的示例项目,在您的 JWVideoView class 中发现了以下问题:每次您设置 videoId 属性 时,它都会再次启动 jwPlayer,并再次将该视图读取到堆栈中。
1.解决方案(去掉playerView,设置player为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。解决方案(保留播放器和视图实例并重置播放器的配置)
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
}
}