在表格视图单元格中嵌入视频
Posted
技术标签:
【中文标题】在表格视图单元格中嵌入视频【英文标题】:Embedding videos in a tableview cell 【发布时间】:2015-11-13 22:02:55 【问题描述】:将视频嵌入 UITableViewCell 的最佳方式是什么?我正在尝试构建类似于 Vine/Instagram 的东西。
我能够使用 SD_WebImage 很好地处理异步图像加载..但不幸的是它们不支持视频。我还尝试使用 MPMoviePlayer 嵌入,但它只是显示为黑屏。这是我尝试过的:
override func viewDidLoad()
super.viewDidLoad()
tableView.frame = CGRectMake(0, 0, view.bounds.width, view.bounds.height);
tableView.delegate = self
tableView.dataSource = self
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
self.view.addSubview(tableView)
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
var cell:UITableViewCell = tableView.dequeueReusableCellWithIdentifier("cell") as! UITableViewCell
var moviePlayer : MPMoviePlayerController?
let url = NSURL (string: "http://jplayer.org/video/m4v/Big_Buck_Bunny_Trailer.m4v")
moviePlayer = MPMoviePlayerController(contentURL: url)
if let player = moviePlayer
player.view.frame = CGRectMake(0, 100, view.bounds.size.width, 180)
player.prepareToPlay()
player.controlStyle = .None
player.repeatMode = .One
player.scalingMode = .AspectFit
cell.addSubview(player.view)
return cell
【问题讨论】:
【参考方案1】:我只测试了视频演示,
这是实现它的方法- 创建一个自定义 Cell 类来保存视频播放器视图,并在此处自己处理 avplayer 的播放和暂停方法。
这是我的自定义单元类 -
import UIKit
import AVFoundation
class VideoCellTableViewCell: UITableViewCell
// I have put the avplayer layer on this view
@IBOutlet weak var videoPlayerSuperView: UIView!
var avPlayer: AVPlayer?
var avPlayerLayer: AVPlayerLayer?
var paused: Bool = false
//This will be called everytime a new value is set on the videoplayer item
var videoPlayerItem: AVPlayerItem? = nil
didSet
/*
If needed, configure player item here before associating it with a player.
(example: adding outputs, setting text style rules, selecting media options)
*/
avPlayer?.replaceCurrentItem(with: self.videoPlayerItem)
override func awakeFromNib()
super.awakeFromNib()
//Setup you avplayer while the cell is created
self.setupMoviePlayer()
func setupMoviePlayer()
self.avPlayer = AVPlayer.init(playerItem: self.videoPlayerItem)
avPlayerLayer = AVPlayerLayer(player: avPlayer)
avPlayerLayer?.videoGravity = AVLayerVideoGravityResizeAspect
avPlayer?.volume = 3
avPlayer?.actionAtItemEnd = .none
// You need to have different variations
// according to the device so as the avplayer fits well
if UIScreen.main.bounds.width == 375
let widthRequired = self.frame.size.width - 20
avPlayerLayer?.frame = CGRect.init(x: 0, y: 0, width: widthRequired, height: widthRequired/1.78)
else if UIScreen.main.bounds.width == 320
avPlayerLayer?.frame = CGRect.init(x: 0, y: 0, width: (self.frame.size.height - 120) * 1.78, height: self.frame.size.height - 120)
else
let widthRequired = self.frame.size.width
avPlayerLayer?.frame = CGRect.init(x: 0, y: 0, width: widthRequired, height: widthRequired/1.78)
self.backgroundColor = .clear
self.videoPlayerSuperView.layer.insertSublayer(avPlayerLayer!, at: 0)
// This notification is fired when the video ends, you can handle it in the method.
NotificationCenter.default.addObserver(self,
selector: #selector(self.playerItemDidReachEnd(notification:)),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
object: avPlayer?.currentItem)
func stopPlayback()
self.avPlayer?.pause()
func startPlayback()
self.avPlayer?.play()
// A notification is fired and seeker is sent to the beginning to loop the video again
func playerItemDidReachEnd(notification: Notification)
let p: AVPlayerItem = notification.object as! AVPlayerItem
p.seek(to: kCMTimeZero)
然后是你的控制器 -
不要忘记导入AVFoundation
框架
import UIKit
import AVFoundation
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource
// The current VisibleIndexPath,
//it can be an array, but for now,
//i am targetting one cell only
//var visibleIP : IndexPath?
var aboutToBecomeInvisibleCell = -1
var avPlayerLayer: AVPlayerLayer!
var videoURLs = Array<URL>()
var firstLoad = true
@IBOutlet weak var feedTableView: UITableView!
override func viewDidLoad()
super.viewDidLoad()
feedTableView.delegate = self
feedTableView.dataSource = self
//Your model to hold the videos in the video URL
for i in 0..<2
let url = Bundle.main.url(forResource:"\(i+1)", withExtension: "mp4")
videoURLs.append(url!)
// initialized to first indexpath
visibleIP = IndexPath.init(row: 0, section: 0)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
return 5
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
return 290
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
return 0
然后在 cellForRow 委托中提供您的 URL
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
//Thats it, just provide the URL from here, it will change with didSet Method in your custom cell class
let cell = self.feedTableView.dequeueReusableCell(withIdentifier: "videoCell") as! VideoCellTableViewCell
cell.videoPlayerItem = AVPlayerItem.init(url: videoURLs[indexPath.row % 2])
return cell
可见单元格的所有部分都在这里管理,我这里使用了所有可见单元格的交集计算,
找到visible IndexPath
,使用它来获取自定义tablecell
类型的单元格。
这也可以通过visibleCells
实现,但我避免了这种情况,因为您可以拥有多种类型的单元格,其中包含图像、文本或其他内容。
func scrollViewDidScroll(_ scrollView: UIScrollView)
let indexPaths = self.feedTableView.indexPathsForVisibleRows
var cells = [Any]()
for ip in indexPaths!
if let videoCell = self.feedTableView.cellForRow(at: ip) as? VideoCellTableViewCell
cells.append(videoCell)
let cellCount = cells.count
if cellCount == 0 return
if cellCount == 1
if visibleIP != indexPaths?[0]
visibleIP = indexPaths?[0]
if let videoCell = cells.last! as? VideoCellTableViewCell
self.playVideoOnTheCell(cell: videoCell, indexPath: (indexPaths?.last)!)
if cellCount >= 2
for i in 0..<cellCount
let cellRect = self.feedTableView.rectForRow(at: (indexPaths?[i])!)
let intersect = cellRect.intersection(self.feedTableView.bounds)
// curerntHeight is the height of the cell that
// is visible
let currentHeight = intersect.height
print("\n \(currentHeight)")
let cellHeight = (cells[i] as AnyObject).frame.size.height
// 0.95 here denotes how much you want the cell to display
// for it to mark itself as visible,
// .95 denotes 95 percent,
// you can change the values accordingly
if currentHeight > (cellHeight * 0.95)
if visibleIP != indexPaths?[i]
visibleIP = indexPaths?[i]
print ("visible = \(indexPaths?[i])")
if let videoCell = cells[i] as? VideoCellTableViewCell
self.playVideoOnTheCell(cell: videoCell, indexPath: (indexPaths?[i])!)
else
if aboutToBecomeInvisibleCell != indexPaths?[i].row
aboutToBecomeInvisibleCell = (indexPaths?[i].row)!
if let videoCell = cells[i] as? VideoCellTableViewCell
self.stopPlayBack(cell: videoCell, indexPath: (indexPaths?[i])!)
使用这些方法来处理播放。
func playVideoOnTheCell(cell : VideoCellTableViewCell, indexPath : IndexPath)
cell.startPlayback()
func stopPlayBack(cell : VideoCellTableViewCell, indexPath : IndexPath)
cell.stopPlayback()
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath)
print("end = \(indexPath)")
if let videoCell = cell as? VideoCellTableViewCell
videoCell.stopPlayback()
如果有兴趣,you can check the demo here
【讨论】:
我爱你! 还有一件事,我有 3 个视频同时显示.. 有时它们只是不启动。 你知道如何缓存视频吗? 抱歉 Farid,还没有尝试缓存视频,如果我尝试这个,会告诉你的。谢谢你的爱:) 不得不对代码进行一些调整,因为我使用的是 collectionView 但这绝对有效【参考方案2】:MPMoviePlayerController
在 ios 9 中已被弃用,您应该改用 AVPlayer
,如下所示:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
let videoURL = NSURL(string: "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")
let player = AVPlayer(URL: videoURL!)
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = cell.bounds
cell.layer.addSublayer(playerLayer)
player.play()
return cell
你需要包含这两个框架:
import AVKit
import AVFoundation
要获取UITableView
中的可见单元格/行,您可以使用两个只读属性:
visibleCells
indexPathsForVisibleRows
希望对你有所帮助。
【讨论】:
这行得通,但是当我尝试执行多个单元格时,它确实会出现故障......尤其是当我尝试滚动时。关于如何解决这个问题的任何想法? 您需要在cellForRowAtIndexPath
中处理您需要显示的内容。你到底需要做什么?
我需要像 vine 这样的东西,一旦用户滚动到一个单元格,视频就会开始播放。我在想,最初我可以只显示视频的初始帧(作为图像),然后当单元格变得可见时,开始播放视频。我很难弄清楚如何做到这一点。是否有任何 tableView 函数可以让我检查 visibleRow?
是的,确实有两个只读属性可以使用,请阅读更新后的答案
好的,所以我能够检测到哪个单元格可见,但是如何更新单元格?我应该使用滚动视图委托并检测滚动何时停止吗?以上是关于在表格视图单元格中嵌入视频的主要内容,如果未能解决你的问题,请参考以下文章