在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 单元格中停止加载

Swift 表格视图,在表格视图单元格中播放视频,内存泄漏

如何在iphone中以横向模式播放视频

停止在 UITableView 的嵌套 UICollectionView 中播放视频或仅在嵌套结构中的可见单元格上播放

完全可见时在 UITableView 中播放视频

如何在 Ipad 中播放来自 URL 的视频?