在uitableview单元格中播放视频

时间:2016-03-23 01:59:03

标签: ios uitableview avfoundation

我正在尝试设置可以播放视频的UITableView。之前的许多SO问题都使用了MPMoviePlayer(Playing Video into UITableViewPlaying video in UItableView in SWIFTPlaying Video From UITableView),现在已经在iOS 9中弃用了。使用AVFoundation的少数人之一(我是什么&#39 ; m using),这是一个: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;

根据我的理解,我需要在播放之前异步下载视频。我以前异步下载过图像,并且下载了#34;其中一部分总是涉及转换NSURL - > NSData - > UIImage的。然后当你拥有UIImage时,你已准备好在单元格中显示它,所以你将主队列调高并调度dispas_async并执行cell.imageView.image = yourImage;在主队列上。

在这里,我有一个NSURL,但是我不太明白这里应该在主队列上执行哪些步骤,哪些步骤应该在主线程之外。我尝试了上述SO问题中的建议,但到目前为止它还没有工作。表格单元格只是加载,视频永远不会播放。我只看到第一帧。有时它的前1秒会播放,但之后它会缓冲并且不会被激活。我正在使用目前只有1个对象的表视图,所以只有1个视频可以播放,但它仍然没有播放。

我做错了什么,有人可以帮我解释一下主要线程上需要哪些部分以及哪些部分关闭?我在顶部提到的帖子中的响应者说,有很多关于此的教程,"但在扫描谷歌时,我没有看到任何。术语"异步"和" iOS"几乎总是得到关于图像下载的搜索结果,而不是视频。但是如果存在任何教程,那么很高兴看到它。

感谢

3 个答案:

答案 0 :(得分:2)

以下是我用于在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的原因。

希望这可以帮助你:)

答案 1 :(得分:0)

  • 在这里,我准备了一个演示,其中根据您的要求,一旦您的单元格可见,播放器将开始播放视频,您可以参考下图来设计故事板。

    //
    //  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!
    }
    
  • 故事板参考图像,其中我创建了具有集合视图单元格的简单自定义视频播放器:

enter image description here

答案 2 :(得分:0)

很快,您可以使用下面的库代码在tableView单元格中播放视频:

https://github.com/MillmanY/MMPlayerView