在uitableview单元格中播放视频
Posted
技术标签:
【中文标题】在uitableview单元格中播放视频【英文标题】:playing video in uitableview cell 【发布时间】:2016-03-23 01:59:02 【问题描述】:我正在尝试设置一个可以播放视频的 UITableView。以前关于此的许多 SO 问题都使用了 MPMoviePlayer(Playing Video into UITableView, Playing video in UItableView in SWIFT, Playing Video From UITableView),现在在 ios 9 中已弃用。少数使用 AVFoundation (我正在使用的)之一就是这个:Play video on UITableViewCell when it is completely visible 是我从中获取大部分代码的地方。这是我的代码,在 cellForRowAtIndexPath 内:
VideoCell *videoCell = (VideoCell *)[self.tableView dequeueReusableCellWithIdentifier:@"VideoCell"];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
NSURL *url = [[NSURL alloc] initWithString:urlString];
dispatch_async(queue, ^
videoCell.item = [AVPlayerItem playerItemWithURL:url];
dispatch_sync(dispatch_get_main_queue(), ^
videoCell.player = [[AVPlayer alloc] initWithPlayerItem:videoCell.item];
videoCell.player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:videoCell.player];
playerLayer.frame = CGRectMake(0, 0, videoCell.contentView.frame.size.width, videoCell.contentView.frame.size.height);
[videoCell.contentView.layer addSublayer:playerLayer];
playerLayer.videoGravity = AVLayerVideoGravityResize;
[videoCell.player play];
);
);
return videoCell;
据我了解,我需要在播放视频之前异步下载视频。我以前异步下载过图片,其中的“下载”部分总是涉及转换 NSURL -> NSData -> UIImage。然后,当您拥有 UIImage 时,您已准备好将其显示在单元格中,因此您将主队列和 dispatch_async 并执行 cell.imageView.image = yourImage;在主队列上。
在这里,我有一个 NSURL,但我不太明白这里的哪些步骤应该在主队列上,哪些应该在主线程之外。我尝试了上述 SO 问题中的建议,但到目前为止它不起作用。表格单元格刚刚加载,视频从不播放。我只看到第一帧。有时它的前 1 秒会播放,但之后会缓冲并且不会播放。我正在使用一个只有 1 个对象的表格视图,因此只有 1 个视频要播放,但它仍然没有播放。
我做错了什么,有人可以帮我解释一下哪些部分需要在主线程上,哪些部分需要关闭?我在顶部提到的线程中的响应者说“那里有很多关于这个的教程”,但是在扫描谷歌时我没有看到任何。术语“异步”和“iOS”几乎总是一起获得有关图像下载的搜索结果,而不是视频。但是,如果有任何教程,我会很高兴看到一个。
谢谢
【问题讨论】:
【参考方案1】:以下是我用来在 tableview 单元格中播放视频的方法。我不确定这是不是最好的方法,无论如何它可能会帮助你:)
首先我创建了一个自定义单元格。 在 setter 方法中我调用了一个方法来设置 AVPlayer。
- (void)setUpAVPlayer
@try
self.videoPlayer = [[AVPlayer alloc]initWithPlayerItem:self.videoPlayerItem];
@catch (NSException *exception)
NSLog(@"Exception : %@",exception.description);
[self.videoPlayer replaceCurrentItemWithPlayerItem:self.videoPlayerItem];
// Setting player properties.
playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.videoPlayer];
playerLayer.videoGravity = AVLayerVideoGravityResize;
[self.previewImageView.layer addSublayer:playerLayer];
self.previewImageView.clipsToBounds = YES;
playerLayer.hidden = YES;
[playerLayer addObserver:self forKeyPath:@"readyForDisplay" options:0 context:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pauseAllRunningPlayers) name:@"pause" object:nil];
// AVPlayer KVO
[self.videoPlayer addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:kRateDidChangeKVO];
[self.videoPlayer addObserver:self forKeyPath:@"currentItem.status" options:NSKeyValueObservingOptionNew context:kStatusDidChangeKVO];
[self.videoPlayer addObserver:self forKeyPath:@"currentItem.duration" options:NSKeyValueObservingOptionNew context:kDurationDidChangeKVO];
[self.videoPlayer addObserver:self forKeyPath:@"currentItem.loadedTimeRanges" options:NSKeyValueObservingOptionNew context:kTimeRangesKVO];
[self.videoPlayer addObserver:self forKeyPath:@"currentItem.playbackBufferFull" options:NSKeyValueObservingOptionNew context:kBufferFullKVO];
[self.videoPlayer addObserver:self forKeyPath:@"currentItem.playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:kBufferEmptyKVO];
[self.videoPlayer addObserver:self forKeyPath:@"currentItem.error" options:NSKeyValueObservingOptionNew context:kDidFailKVO];
[videoLoadingIndicator stopAnimating];
我发现在你的代码中你写了这样的播放函数[videoCell.player play];
只有在播放器状态变为“AVPlayerStatusReadyToPlay”时才应调用播放函数。这就是我使用 KVO 的原因。
希望这对你有帮助:)
【讨论】:
所以你在 cellForRowAtIndexPath 中调用它? 不,我创建了一个自定义单元类,并在数据设置器方法中调用了上述函数 @BasilMariano:对不起,我没有样品。【参考方案2】:在这里我准备了一个演示,一旦您的单元格根据您的要求可见,播放器将开始播放视频,您可以参考下图设计您的故事板。
//
// ViewController.swift
// RTLDemo
//
// Created by iOS Test User on 06/01/18.
// Copyright © 2018 iOS Test User. All rights reserved.
//
import UIKit
import AVKit
public struct VideoName
static let video1 = "video1"
static let video2 = "video2"
public struct MediaType
static let video = 1
static let image = 2
class ViewController: UIViewController
@IBOutlet weak var collView: UICollectionView!
var timer: Timer?
var isVideoPlaying: Bool = false
// ------------------------------------------------------------------------------------------
// MARK: -
// MARK: - Memory management method
// ------------------------------------------------------------------------------------------
override func didReceiveMemoryWarning()
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
// ------------------------------------------------------------------------------------------
// MARK:
// MARK: - Custom Methods
// ------------------------------------------------------------------------------------------
func initialSetup()
if self.timer == nil
self.timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(checkForTheVisibleVideo), userInfo: nil, repeats: true)
timer?.fire()
// ------------------------------------------------------------------------------------------
@objc func checkForTheVisibleVideo()
if !isVideoPlaying
let visibleCell = self.collView.indexPathsForVisibleItems
if visibleCell.count > 0
for indexPath in visibleCell
if self.isVideoPlaying
break
if let cell = self.collView.cellForItem(at: indexPath) as? CustomCell,cell.mediaType == MediaType.video
if cell.player == nil
cell.player = AVPlayer(url: URL.init(fileURLWithPath: Bundle.main.path(forResource: VideoName.video2, ofType: "mp4")!))
cell.playerLayer = AVPlayerLayer.init(player: cell.player)
cell.playerLayer?.frame = cell.imgView.frame
cell.imgView.layer.addSublayer(cell.playerLayer!)
NotificationCenter.default.addObserver(self, selector: #selector(videoDidFinishPlaying), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: cell.player?.currentItem)
cell.player?.addPeriodicTimeObserver(forInterval: CMTime.init(seconds: 1, preferredTimescale: 1), queue: .main, using: (time) in
if cell.player?.currentItem?.status == .readyToPlay
let timeDuration : Float64 = CMTimeGetSeconds((cell.player?.currentItem?.asset.duration)!)
cell.lblDuration.text = self.getDurationFromTime(time: timeDuration)
let currentTime : Float64 = CMTimeGetSeconds((cell.player?.currentTime())!)
cell.lblStart.text = self.getDurationFromTime(time: currentTime)
cell.slider.maximumValue = Float(timeDuration.rounded())
cell.slider.value = Float(currentTime.rounded())
)
cell.player?.play()
cell.btnPlay.setImage(#imageLiteral(resourceName: "pause_video"), for: .normal)
self.isVideoPlaying = true
// ------------------------------------------------------------------------------------------
@objc func videoDidFinishPlaying()
self.isVideoPlaying = false
let visibleItems: Array = self.collView.indexPathsForVisibleItems
if visibleItems.count > 0
for currentCell in visibleItems
guard let cell = self.collView.cellForItem(at: currentCell) as? CustomCell else
return
if cell.player != nil
cell.player?.seek(to: kCMTimeZero)
cell.player?.play()
// ------------------------------------------------------------------------------------------
@objc func onSliderValChanged(slider: UISlider, event: UIEvent)
if let touchEvent = event.allTouches?.first
guard let cell = self.collView.cellForItem(at: IndexPath.init(item: slider.tag, section: 0)) as? CustomCell else
return
switch touchEvent.phase
case .began:
cell.player?.pause()
case .moved:
cell.player?.seek(to: CMTimeMake(Int64(slider.value), 1))
case .ended:
cell.player?.seek(to: CMTimeMake(Int64(slider.value), 1))
cell.player?.play()
default:
break
// ------------------------------------------------------------------------------------------
func getDurationFromTime(time: Float64)-> String
let date : Date = Date(timeIntervalSince1970: time)
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone.init(identifier: "UTC")
dateFormatter.dateFormat = time < 3600 ? "mm:ss" : "HH:mm:ss"
return dateFormatter.string(from: date)
// ------------------------------------------------------------------------------------------
@IBAction func btnPlayTapped(_ sender: UIButton)
let indexPath = IndexPath.init(item: sender.tag, section: 0)
guard let cell = self.collView.cellForItem(at: indexPath) as? CustomCell else
return
if isVideoPlaying
self.isVideoPlaying = false
cell.btnPlay.setImage(#imageLiteral(resourceName: "play_video"), for: .normal)
cell.player?.pause()
else
if cell.player == nil
cell.player = AVPlayer(url: URL.init(fileURLWithPath: Bundle.main.path(forResource: VideoName.video2, ofType: "mp4")!))
cell.playerLayer = AVPlayerLayer(player: cell.player!)
cell.playerLayer?.frame = CGRect.init(x: 0, y: 0, width: UIScreen.main.bounds.size.width - 50, height: (UIScreen.main.bounds.size.height - 64) * 0.3)
cell.imgView.layer.addSublayer(cell.playerLayer!)
NotificationCenter.default.addObserver(self, selector: #selector(videoDidFinishPlaying), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: cell.player?.currentItem)
cell.btnPlay.setImage(#imageLiteral(resourceName: "pause_video"), for: .normal)
cell.player?.play()
self.isVideoPlaying = true
// ------------------------------------------------------------------------------------------
// MARK: -
// MARK: - View life cycle methods
// ------------------------------------------------------------------------------------------
override func viewDidLoad()
super.viewDidLoad()
self.initialSetup()
extension ViewController: UICollectionViewDataSource, UICollectionViewDelegate,UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
return 10
// ------------------------------------------------------------------------------------------
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
let cell = self.collView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
if indexPath.row % 2 == 0
cell.mediaType = MediaType.image
cell.btnPlay.isHidden = true
cell.lblDuration.isHidden = true
cell.lblStart.isHidden = true
cell.slider.isHidden = true
cell.imgView.isHidden = false
else
cell.mediaType = MediaType.video
cell.btnPlay.isHidden = false
cell.lblDuration.isHidden = false
cell.lblStart.isHidden = false
cell.slider.isHidden = false
cell.imgView.isHidden = false
cell.btnPlay.tag = indexPath.row
cell.slider.tag = indexPath.row
cell.btnPlay.addTarget(self, action: #selector(btnPlayTapped(_:)), for: .touchUpInside)
cell.slider.addTarget(self, action: #selector(self.onSliderValChanged(slider:event:)), for: .valueChanged)
return cell
// ------------------------------------------------------------------------------------------
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
return CGSize.init(width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height / 3)
// ------------------------------------------------------------------------------------------
func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath)
guard let cellToHide = cell as? CustomCell else
return
if cellToHide.player != nil
cellToHide.player?.pause()
cellToHide.playerLayer?.removeFromSuperlayer()
cellToHide.player = nil
cellToHide.btnPlay.setImage(#imageLiteral(resourceName: "play_video"), for: .normal)
self.isVideoPlaying = false
// Custom Cell Class
class CustomCell: UICollectionViewCell
@IBOutlet weak var btnPlay: UIButton!
@IBOutlet weak var lblDuration: UILabel!
@IBOutlet weak var imgView: UIImageView!
@IBOutlet weak var viewBack: UIView!
@IBOutlet weak var lblStart: UILabel!
@IBOutlet weak var slider: UISlider!
var player: AVPlayer?
var playerLayer: AVPlayerLayer?
var mediaType: Int!
故事板参考图像,我在其中创建了带有集合视图单元格的简单自定义视频播放器:
【讨论】:
你有没有演示项目 兄弟我想根据屏幕显示视频的全高和全宽【参考方案3】:在 swift 中,您可以使用以下库代码在 tableView 单元格中播放视频:
https://github.com/MillmanY/MMPlayerView
【讨论】:
以上是关于在uitableview单元格中播放视频的主要内容,如果未能解决你的问题,请参考以下文章
滚动 tableView 后,视频在 AVPlayerController 的 UITableView 单元格中停止加载