在表格视图单元格中嵌入视频

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】:

MPMoviePlayerControllerios 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? 是的,确实有两个只读属性可以使用,请阅读更新后的答案 好的,所以我能够检测到哪个单元格可见,但是如何更新单元格?我应该使用滚动视图委托并检测滚动何时停止吗?

以上是关于在表格视图单元格中嵌入视频的主要内容,如果未能解决你的问题,请参考以下文章

点击表格视图单元格中的视频以在视图控制器中打开它

访问嵌入在单元格中的 textField

将表格视图快速添加到表格视图单元格中的问题

在表格视图单元格中使用按钮出列单元格

图像在表格视图单元格中下载后调整大小

表格视图单元格作为按钮