How to loop video with AVPlayerLooper - ios

I try to loop a video in a TV OS app with the AVPlayerLooper because this should get rid of the pause/hicup when playing the video again. I watched the WWDC2016 video https://developer.apple.com/videos/play/wwdc2016/503/ and try to implement the code but it doesn't loop.
I have one PlayerViewController which inherits AVPlayerViewController. I put the code to let the video loop. If I have the following code, it shows nothing. If I change the second line to self.queuePlayer = AVQueuePlayer(playerItem:playerItem), it only plays once.
let playerItem = AVPlayerItem(url: url as URL)
self.queuePlayer = AVQueuePlayer() //I declared this as a variable in the view controller
self.playerLayer = AVPlayerLayer(player: self.queuePlayer) //I declared this as a variable in the view controller
let playerLooper = AVPlayerLooper(player: self.queuePlayer!, templateItem: playerItem)
self.view.layer.addSublayer(self.playerLayer!)
self.playerLayer?.frame = self.view.frame
self.queuePlayer?.play()
Have any of you succeeded in playing looped video with the latest AVPlayerLooper?

I fixed the problem myself.
The playerLooper must be a member variable in the class otherwise it doesn't work because a local variable is gone after the method has been called. So I put this line at the beginning of the class to declare it. I didn't declare it as an AVPlayerLooper because this is only for tvos10.0 and newer versions. I want my class to be adaptive to tvos9.0.
This is my working code.
var playerLooper: NSObject?
var playerLayer:AVPlayerLayer!
var queuePlayer: AVQueuePlayer?
func playVideo(_ filmName: String){
if let path = Bundle.main.path(forResource: filmName, ofType: "mov") {
let url = URL(fileURLWithPath: path)
if #available(tvOS 10.0, *) {
// Use a new player looper with the queue player and template item
let playerItem = AVPlayerItem(url: url as URL)
self.player = AVQueuePlayer(items: [playerItem])
self.playerLayer = AVPlayerLayer(player: self.player)
self.playerLooper = AVPlayerLooper(player: self.player! as! AVQueuePlayer, templateItem: playerItem)
self.view.layer.addSublayer(self.playerLayer!)
self.playerLayer?.frame = self.view.frame
self.player?.play()
} else {
// Fallback on earlier versions, this solution has hicup at end
player = AVPlayer(url: url)
player?.play()
loopVideo(player!)
}
}
}
func loopVideo(_ videoPlayer: AVPlayer) {
NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil, queue: nil) { notification in
if(!self.isStopped){
videoPlayer.seek(to: kCMTimeZero)
videoPlayer.play()
}
}
}

Objective-C version:
#implementation ViewController
{
// As explained in accepted answer, the playerLooper must be a
// member (I put everything as member for simplicity):
AVPlayerItem *_playerItem;
AVQueuePlayer *_player;
AVPlayerLooper *_playerLooper;
AVPlayerLayer *_playerLayer;
}
- (void)viewDidLoad {
[super viewDidLoad];
// ...
NSString *videoFile = [[NSBundle mainBundle] pathForResource:#"example" ofType:#"mov"];
NSURL *videoURL = [NSURL fileURLWithPath:videoFile];
_playerItem = [AVPlayerItem playerItemWithURL:videoURL];
_player = [AVQueuePlayer queuePlayerWithItems:#[_playerItem]];
_playerLooper = [AVPlayerLooper playerLooperWithPlayer:_player templateItem:_playerItem];
_playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
_playerLayer.frame = self.view.bounds;
[self.view.layer addSublayer:_playerLayer];
[_player play];
}

Swift 5 custom class for playing a video looped without any glitches
import Foundation
import AVKit
class VideoPlayerLooped {
public var videoPlayer:AVQueuePlayer?
public var videoPlayerLayer:AVPlayerLayer?
var playerLooper: NSObject?
var queuePlayer: AVQueuePlayer?
func playVideo(fileName:String, inView:UIView){
if let path = Bundle.main.path(forResource: fileName, ofType: "mov") {
let url = URL(fileURLWithPath: path)
let playerItem = AVPlayerItem(url: url as URL)
videoPlayer = AVQueuePlayer(items: [playerItem])
playerLooper = AVPlayerLooper(player: videoPlayer!, templateItem: playerItem)
videoPlayerLayer = AVPlayerLayer(player: videoPlayer)
videoPlayerLayer!.frame = inView.bounds
videoPlayerLayer!.videoGravity = AVLayerVideoGravity.resizeAspectFill
inView.layer.addSublayer(videoPlayerLayer!)
videoPlayer?.play()
}
}
func remove() {
videoPlayerLayer?.removeFromSuperlayer()
}
}
Usage:
let videoHolder = UIView()
videoHolder.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
var myVideoPlayerLooped = VideoPlayerLooped()
videoPlayerLooped.playVideo(video: "myLocalMovFile", inView: videoHolder)

The problem with your implementation is that you are declaring the AVPlayerLayer on function level whereas it should be declared on class level.

Related

Issue displaying subviews above video | Swift 5

The login screen I am designing is near complete but fails to properly display both the username and password textfields once the view controller loads
The following function executes when the view loads - it properly plays the video and displays both the image and loginButton correctly, it appears to also appears to display the textfields for a split second until the video fully loads and plays.
func playVideo() {
guard let path = Bundle.main.path(forResource: "videoFile", ofType: "mp4") else {
return
}
let asset: AVAsset = AVAsset(url: URL(fileURLWithPath: path))
let playerItem = AVPlayerItem(asset: asset)
let queuePlayer = AVQueuePlayer(playerItem: playerItem)
self.playerLooper = AVPlayerLooper(player: queuePlayer, templateItem: playerItem)
let playerLayer = AVPlayerLayer(player: queuePlayer)
playerLayer.frame = self.view.bounds
playerLayer.videoGravity = .resizeAspectFill
self.videoLayer.layer.addSublayer(playerLayer)
queuePlayer.play()
videoLayer.bringSubviewToFront(img)
videoLayer.bringSubviewToFront(usernameTF)
videoLayer.bringSubviewToFront(passwordTF)
videoLayer.bringSubviewToFront(signinButton)
}
Any ideas what could be causing this?
You can change this code according to your need.
var player : AVPlayer?
var playerViewController : AVPlayerViewController?
func playLiveUrl(url:String){
let videoURL = URL(string: "http://bla blah.com")
self.player = AVPlayer(url: videoURL!)
self.playerViewController = AVPlayerViewController()
playerViewController?.player = self.player
playerViewController?.view.frame = self.topPlayerView.frame
playerViewController?.player?.play()
self.topPlayerView.addSubview(playerViewController?.view ?? UIView()) // Container topPlayerView which contains a player
}
var topPlayerView : UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .black
return view
}()

AVPlayerLayer says always nil

I don't understand why it is not working. I pass a videoURL trough a segue to a another ViewController. This works fine.
In the other ViewController I want to play the video. But for some reason it is not working.
I unwrap the URL with optional binding. But playerLayer does not accept the URL. It says always nil.
This is the URL: VideoURL Optional(file:///var/mobile/Containers/Data/Application/517BA864-F5CB-4E2C-BE54-D42555569266/Documents/3d.mov)
My code:
var player: AVPlayer?
var playerLayer: AVPlayerLayer?
var videoURL: URL?
func playVideo() {
if let _videoURL = videoURL {
player = AVPlayer(url: _videoURL)
playerLayer = AVPlayerLayer(layer: player!)
playerLayer?.frame = contentView.layer.bounds
playerLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
self.contentView.layer.addSublayer(playerLayer!)
player?.play()
}
Try creating an AVURLAsset and an AVPlayerItem. Then pass that AVPlayerItem into the AVPlayer rather than trying to use the URL init.
I tested this with a sample video I downloaded from here. Note there is no sound in this video.
import UIKit
import AVFoundation
class ViewController: UIViewController {
private let videoUrl = Bundle.main.url(forResource: "file_example_MOV_1920_2_2MB", withExtension: "mov")!
private var videoPlayer: AVPlayer?
private var playerLayer: AVPlayerLayer?
override func viewDidLoad() {
super.viewDidLoad()
playVideo()
}
func playVideo() {
let asset = AVURLAsset(url: videoUrl)
let primaryPlayerItem = AVPlayerItem(asset: asset)
let player = AVPlayer(playerItem: primaryPlayerItem)
let videoLayer = AVPlayerLayer(player: player)
videoLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
videoLayer.frame = view.bounds
view.layer.addSublayer(videoLayer)
videoPlayer = player
playerLayer = videoLayer
videoPlayer?.play()
}
}
Try the below code. It's working.
var player: AVPlayer!
var playerLayer: AVPlayerLayer!
func playVideo() {
if let url = videoURL {
do {
let item:AVPlayerItem = AVPlayerItem(url: url)
player = AVPlayer(playerItem: item)
playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = contentView.bounds
playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
self.contentView.layer.addSublayer(playerLayer)
player.play()
} catch {
print("Error1", error.localizedDescription)
}
}
}

Seamless looping Video using AVPlayer- Swift

I am using AVPlayer to play local video in background using loop and video is playing fine but after finishing video it takes pause to play video in loop. I have tried many methods and also seen many post on stack overflow but i failed to find appropriate solution. I am using Swift3.
Code is here :
var videoplayer :AVPlayer = AVPlayer()
override func viewDidLoad() {
super.viewDidLoad()
let path = Bundle.main.path(forResource: "background4", ofType: "mp4")
videoplayer = AVPlayer(url: URL(fileURLWithPath: path!))
videoplayer.volume = 0
videoplayer.actionAtItemEnd = AVPlayerActionAtItemEnd.none;
let playerLayer = AVPlayerLayer(player: videoplayer)
playerLayer.frame = self.view.frame
playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
if (videoplayer.rate != 0) {
print("playing videoplayer")
self.blurBgImage.isHidden = true
}
playerLayer.zPosition = -1
videoplayer.rate = 0
videoplayer.play()
self.blurBgImage.layer.addSublayer(playerLayer)
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: videoplayer.currentItem, queue: nil, using: { (_) in
DispatchQueue.main.async {
let t1 = CMTimeMake(5, 100)
self.videoplayer.seek(to: t1)
self.videoplayer.play()
}
})
}
I have also tried AVPlayerLooper.
Code is :
var playerLooper: NSObject?
var playerLayer:AVPlayerLayer!
var queuePlayer: AVQueuePlayer?
func playVideo(_ filmName: String){
if let path = Bundle.main.path(forResource: filmName, ofType: "mp4") let url = URL(fileURLWithPath: path)
if #available(tvOS 10.0, *) {
let playerItem = AVPlayerItem(url: url as URL)
self.videoplayer = AVQueuePlayer(items: [playerItem])
self.playerLayer = AVPlayerLayer(player: self.videoplayer)
self.playerLooper = AVPlayerLooper(player: self.videoplayer as! AVQueuePlayer, templateItem: playerItem)
self.blurBgImage.layer.addSublayer(playerLayer!)
self.playerLayer?.frame = self.view.frame
self.videoplayer.volume = 10
self.videoplayer.play()
} else {
videoplayer = AVPlayer(url: url)
videoplayer.play()
loopVideo(videoplayer)
}
}
}
What should i do for seamless looping? Thanks in Advance.
FYI there is sample code here: https://developer.apple.com/library/content/samplecode/avloopplayer/Introduction/Intro.html
#matt's deleted answer works fine for me (on device / simulator) for iOS 10+ devices:
Use AVPlayerLooper. That is exactly what it is for.
https://developer.apple.com/reference/avfoundation/avplayerlooper
Basically it implements AVQueuePlayer for you, constantly updating the
queue so that it never ends.
It seamlessly loops without any white/black blip.
E.g.
private var looper: AVPlayerLooper?
...
let queuePlayer = AVQueuePlayer(playerItem: item)
looper = AVPlayerLooper(player: queuePlayer, templateItem: item)
videoPlayerLayer.player = queuePlayer
If you end up doing this in a reusable cell (e.g. UICollectionView), then make sure you disable looping before cell re-use or you'll get some obscure crashes:
looper?.disableLooping()

Swift: Resize an AVPlayerLayer to match the bounds of its parent container

I have a local video playing using AVPlayer and AVPlayerLayer (Swift, iOS 8.2), and i need to resize the AVPlayerLayer (introPlayerLayer) when its parent view (introView) is resized, including on orientation change. So far nothing I have tried seems to work and the playerLayer has the wrong size and position.
The AVPlayer is set up using a local mp4 video, and the AVPlayerLayer is initialized using that player. The layer is then added as a sublayer to the parent view.
Here is my code:
func playIntro() {
let path = NSBundle.mainBundle().pathForResource("intro", ofType: "mp4")
let url = NSURL.fileURLWithPath(path!)
let introPlayer = AVPlayer(URL: url)
introPlayer.allowsExternalPlayback = false
var introPlayerLayer = AVPlayerLayer(player: introPlayer)
introView.layer.addSublayer(introPlayerLayer)
introPlayerLayer.frame = introView.bounds
NSNotificationCenter.defaultCenter().addObserver(self, selector: "playerDidFinishPlaying:", name: AVPlayerItemDidPlayToEndTimeNotification, object: introPlayer.currentItem)
introView.hidden = false
introPlayer.play()
}
Should I be adding the AVPlayerLayer as a sublayer of the UIView, or should I be subclassing the parent container (UIView) as shown in the "The Player View" section of https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/AVFoundationPG/Articles/02_Playback.html. If so, how would I write that PlayerView subclass (shown in Objective-C) in Swift?
Thank you in advance for any assistance.
Swift 3.
private func playVideo() {
guard let path = Bundle.main.path(forResource: "video", ofType:"mp4") else {
debugPrint("video.m4v not found")
return
}
let player = AVPlayer(url: URL(fileURLWithPath: path))
var playerLayer: AVPlayerLayer?
playerLayer = AVPlayerLayer(player: player)
playerLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
playerLayer!.frame = self.view.frame
self.view!.layer.addSublayer(playerLayer!)
player.play()
}
I was able to do what you are trying to do, I think, in my own project. This is without subclassing. Here is my code:
override func viewWillLayoutSubviews() {
previewScene()
}
func previewScene() {
var firstAsset: AVAsset!, secondAsset: AVAsset!, thirdAsset: AVAsset!
firstAsset = MediaController.sharedMediaController.s3Shot1
secondAsset = MediaController.sharedMediaController.s3Shot2
thirdAsset = MediaController.sharedMediaController.s3Shot3
if firstAsset != nil && secondAsset != nil && thirdAsset != nil {
let assets = [firstAsset, secondAsset, thirdAsset]
var shots = [AVPlayerItem]()
for item in assets {
let shot = AVPlayerItem(asset: item)
shots.append(shot)
}
self.videoPlayer = AVQueuePlayer(items: shots)
self.playerLayer = AVPlayerLayer(player: self.videoPlayer)
self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
AVPlayerItemDidPlayToEndTimeNotification, object: self.videoPlayer.items().last)
self.videoPreviewLayer.layer.addSublayer(playerLayer)
self.playerLayer.frame = self.videoPreviewLayer.bounds
}
}
introView.layer.masksToBounds = true
may also be helpful

MPMoviePlayerController shows only black screen - Swift

I have a View inside a ViewController that I want to add a move player controller to it. But so far I'm only getting a black screen.
I'm running this on the simulator, and using the apple dev stream which I tested in Safari and it works.
The videoView is added to the controller through an IBOutlet. I'm using Xcode 6 beta 7.
This is all inside a UIViewController.
Declaration of videoView 320x320 (global):
#IBOutlet var videoView: UIView!
Declaration of MPMoviePlayerController (global):
var videoPlayer : MPMoviePlayerController = MPMoviePlayerController()
Adding videoPlayer to View:
videoURLWithPath = "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8"
let videoURL = NSURL(fileURLWithPath: videoURLWithPath)
videoPlayer.movieSourceType = MPMovieSourceType.Streaming;
videoPlayer.contentURL = videoURL
videoPlayer.view.frame = videoView.bounds
videoView.addSubview(videoPlayer.view)
videoPlayer.controlStyle = MPMovieControlStyle.Embedded
videoPlayer.prepareToPlay()
videoPlayer.play()
Storyboard:
Simulator:
Update:
I've also tried doing this. Super simple. And I'm still getting the same result. The frame size is set that way so that I can see that the player has actually been added.
let streamURL = NSURL(string: "http://www.thumbafon.com/code_examples/video/segment_example/prog_index.m3u8")
var streamPlayer = MPMoviePlayerController(contentURL: streamURL)
streamPlayer.view.frame = CGRect(x: 10, y: 10, width: 200, height: 200)
streamPlayer.controlStyle = MPMovieControlStyle.Embedded
videoView.addSubview(streamPlayer.view)
streamPlayer.play()
I ended up ditching MPMoviePlayerController and opted to use AVFoundation instead.
Working example:
Global declarations:
var player : AVPlayer? = nil
var playerLayer : AVPlayerLayer? = nil
var asset : AVAsset? = nil
var playerItem: AVPlayerItem? = nil
inside viewDidLoad:
let videoURLWithPath = "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8"
let videoURL = NSURL(string: videoURLWithPath)
asset = AVAsset.assetWithURL(videoURL) as? AVAsset
playerItem = AVPlayerItem(asset: asset)
player = AVPlayer(playerItem: self.playerItem)
playerLayer = AVPlayerLayer(player: self.player)
playerLayer!.frame = videoView.frame
videoView.layer.addSublayer(self.playerLayer)
player!.play()
Rather than using MPMoviePlayerController, this works perfectly for me:
// import MediaPlayer
var moviePlayer : MPMoviePlayerViewController?
#IBAction func playVideo() {
let path = NSBundle.mainBundle().pathForResource("ra3", ofType:"mp4")
let url = NSURL.fileURLWithPath(path!)
let videoURL = url
moviePlayer = MPMoviePlayerViewController(contentURL: videoURL )
if let player = moviePlayer {
player.view.frame = self.view.bounds
self.presentViewController(moviePlayer!, animated: true, completion: nil)
}
else {
NSLog("no player")
}
}

Resources