Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

playing video in uitableview cell

Tags:

I am trying to setup a UITableView that can play videos. Many of the previous SO questions on this used MPMoviePlayer(Playing Video into UITableView, Playing video in UItableView in SWIFT, Playing Video From UITableView), which is now deprecated in iOS 9. One of the few that used AVFoundation (what i'm using), is this one: Play video on UITableViewCell when it is completely visible and is where I'm getting most of my code from. Here is my code, inside 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;

From what I understand, I need to asynchronously download the videos before I play them. I've asynchronously downloaded images before, and the "download" part of that always involves converting NSURL -> NSData -> UIImage. And then when you have the UIImage, you're ready to display it in the cell, so you bring the main queue up and dispatch_async and perform the cell.imageView.image = yourImage; on the main queue.

Here, I have an NSURL, but I don't quite get which steps here should be on the main queue and which should be off the main thread. I tried what was suggested in the aforementioned SO question, but so far it isn't working. The table cell just loads, and the video never plays. I just see the first frame. Sometimes the first 1 second of it will play, but then it buffers after that and won't. I'm working with a table view that has only 1 object right now, so there is just 1 video to play, and it still isn't playing.

What am I doing wrong, and could someone help me explain exactly which parts need to be on the main thread and which off? A responder in the thread I mentioned at the top said there were "lots of tutorials on this out there," but upon scanning google I didn't see any. the terms "asynchronous" and "iOS" together almost always get search results about image downloading, not video. But if any tutorials exist it would be nice to see one.

thanks

like image 952
jjjjjjjj Avatar asked Mar 23 '16 01:03

jjjjjjjj


2 Answers

The following is the method that I have used to play video in the tableview cells. I'm not sure it is the best method for this, anyway it may help you :)

First I have created a custom cell. In the setter method I called a method to set 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];

}

I have found that in your code you have written theb play function like this [videoCell.player play];

You should call the play function only when the player state becomes 'AVPlayerStatusReadyToPlay'. Thats why I used KVOs.

Hope this may helps you :)

like image 57
Vishnu Kumar. S Avatar answered Oct 11 '22 03:10

Vishnu Kumar. S


  • Here i have prepared a demo in which Once your cell will be visible as per your requirement player will start play a video, you can design your storyboard with the reference of below image.

    //
    //  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!
    }
    
  • Storyboard reference image in which i created simple custom video player with cell of collection view:

enter image description here

like image 39
Mobile Team iOS-RN Avatar answered Oct 11 '22 03:10

Mobile Team iOS-RN