AVPlayer pause not working TVOS - ios

I am making a tvOS app. I have two tabs A and B in my application. Let say video is playing in tab A in a VC. I want to pause my current playing video when i switch the tabs from A->B. I am doing this in viewWillDisappear but the video only pauses for the first time. When i switch the tabs B->A again and hit play button on TV remote it plays but if i again try to switch the tabs A->B the video continues and it does not stops.
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
avplayerVC?.player?.rate = 0.0
print("Player Playing Rate : ", "\(avplayerVC?.player?.rate)")
}
I am using avPlayer.pause() method and avPlayer.rate = 0.0 to stop it but none of them works every time.
The log says that Player Playing Rate : Optional(0.0). but the video actually does not pauses. Here is my complete code.
class PlayViewController : UIViewController {
var avplayerVC : AVPlayerViewController?
var videoUrlStr : String?
override func viewDidLoad() {
super.viewDidLoad()
print("In PlayViewController View Did Load")
avplayerVC = AVPlayerViewController()
let avAsset = AVAsset(URL: NSURL.init(string: videoUrlStr!)!)
let avPlayerItem = AVPlayerItem(asset: avAsset)
avplayerVC?.player = AVPlayer(playerItem: avPlayerItem)
avplayerVC?.player?.seekToTime(kCMTimeZero)
print("Status 1: " + "\(avplayerVC?.player?.status.rawValue)")
print(self.view?.frame)
// doesn't work
avplayerVC?.view.frame = self.view.frame
self.view.addSubview((avplayerVC?.view!)!)
avplayerVC?.player?.play()
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
print("Changing rate")
avplayerVC?.player?.rate = 0.0
print("Player Playing Rate : ", "\(avplayerVC?.player?.rate)")
}
}
Any help will be highly appreciated.

I was having the same problem with AVPlayerViewController, maybe my answer helps you: here

Related

Too many AVPlayers causes Terminated due to memory issue

I have a vc that has an AVPlayer inside of it. From that vc I can push on a different vc with another player inside of it and I can keep pushing on more vcs with a player inside them also. After about the 14th vc getting pushed on the app crashes with Terminated due to memory issue.
When I look at the memory graph (9th icon in left pane) it's at about 70mb so there isn't an obscene jump in memory. All of my video files are saved to and retrieved from disk and whenever I pop a vc I have a print statement inside Deinit that always runs so there isn't anything else causing the memory issue. This led me to believe the other SO answers that said that there is a limit to 16 AVPlayers at the same time. The reason I think all of these players are causing this memory crash is because once I comment out the player initialization code I can push on 30 vcs with no crashes whatsoever.
I was about to completely remove the player, playerItem, its observers, and player layer from the parent vc in viewWillDisappear/viewDidDisappear and then once the child is popped reinitialize everything again in viewWillAppear/viewDidAppear but then I came across this blog that says
platform limitation on the number of video “render pipelines” shared
between apps on the device. It turns out that setting the AVPlayer to
nil does not free up a playback pipeline and that it is actually the
association of a playerItem with a player that creates the pipeline in
the first place
and this answer that says
It is not a limit on the number of instances of AVPlayer, or
AVPlayerItem. Rather,it is the association of AVPlayerItem with an
AVPlayer which creates a "render pipeline"
The question is when pushing/popping on a new vc (it will have a player inside of it) do I need to completely remove/readd everything associated with the player or will setting the AVPlayerItem to nil then reinitializing it again resolve the issue?
If the render pipelines are causing the problem it would seem that the limit isn't on the players but on the playerItems.
code:
override func viewDidLoad() {
super.viewDidLoad()
configurePlayer(with: self.videoUrl)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// only runs when popping back
if !isMovingToParent {
// I can either
let asset = AVAsset(url: selfvideoUrl)
self.playerItem = AVPlayerItem(asset: asset)
self.player?.replaceCurrentItem(with: playerItem!)
// or just reinitialize everything
configurePlayer(with: self.videoUrl)
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// would these 2 lines be enough suffice to prevent the issue?
self.player?.replaceCurrentItem(with: nil)
self.playerItem = nil
// or do I also need to nil out everything?
self.player = nil
self.avPlayerView.removeFromSuperView()
self.playerStatusObserver = nil
self.playerRateObserver = nil
self.playerTimeControlStatusObserver = nil
}
func configurePlayer(with videoUrl: URL) {
let asset = AVAsset(url: videoUrl)
self.playerItem = AVPlayerItem(asset: asset)
self.player = AVPlayer()
self.playerLayer = AVPlayerLayer(player: player)
self.playerLayer?.videoGravity = AVLayerVideoGravity.resizeAspect
self.player?.automaticallyWaitsToMinimizeStalling = false
self.playerItem.preferredForwardBufferDuration = TimeInterval(1.0)
view.addSubview(avPlayerView) // this is just a view with a CALayer for the playerLayer
self.playerLayer?.frame = avPlayerView.bounds
self.avPlayerView.layer.addSublayer(playerLayer!)
self.avPlayerView.playerLayer = playerLayer
self.player?.replaceCurrentItem(with: playerItem!)
// add endTimeNotification
setNSKeyValueObservers()
}
func setNSKeyValueObservers() {
self.playerStatusObserver = player?.observe(\.currentItem?.status, options: [.new, .old]) {
[weak self] (player, change) in ... }
self.playerRateObserver = player?.observe(\.rate, options: [.new, .old], changeHandler: {
[weak self](player, change) in ... }
self.playerTimeControlStatusObserver = player?.observe(\.timeControlStatus, options: [.new, .old]) {
[weak self](player, change) in ... }
}
I just tested it and setting this to nil and reinitializing it is what allowed me to push on 30 vcs each with an AVPlayer inside of each one without any crashes whatsoever.
player?.replaceCurrentItem(with: nil)
So the issue isn't the total amount of AVPlayers, it's like this guy said, the association of AVPlayerItem with an AVPlayer which creates a "render pipeline and too many of them at the same time is what causes the problem.
override func viewDidLoad() {
super.viewDidLoad()
configurePlayer(with: self.videoUrl)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let playerItem = playerItem {
self.player?.replaceCurrentItem(with: playerItem)
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.player?.replaceCurrentItem(with: nil)
}

IOS Custom UIViewController Error

In my login screen of my Ios app I am trying to display a video. I saw on stack overflow a great example on how to do such thing. I tried the code out for myself and ran into an error. I was going to comment my error on the original post but my account did not have enough reputation to do so. My code when I run on my simulated device returns to me an error that says:
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
I managed to track down there error using breakpoints to line 12 which is avPlayer = AVPlayer(url: video!)
Though here is my full code:
import AVFoundation
import UIKit
class VideoBackgroundController: UIViewController {
var avPlayer: AVPlayer!
var avPlayerLayer: AVPlayerLayer!
var paused: Bool = false
override func viewDidLoad() {
let video = Bundle.main.url(forResource:"space", withExtension: "mp4")
//This is where my the error happens
avPlayer = AVPlayer(url: video!)
avPlayerLayer = AVPlayerLayer(player: avPlayer)
avPlayerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
avPlayer.volume = 0
avPlayer.actionAtItemEnd = .none
avPlayerLayer.frame = view.layer.bounds
view.backgroundColor = .clear
view.layer.insertSublayer(avPlayerLayer, at: 0)
NotificationCenter.default.addObserver(self,
selector: #selector(playerItemDidReachEnd(notification:)),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
object: avPlayer.currentItem)
}
func playerItemDidReachEnd(notification: Notification) {
let p: AVPlayerItem = notification.object as! AVPlayerItem
p.seek(to: kCMTimeZero)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
avPlayer.play()
paused = false
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
avPlayer.pause()
paused = true
}
}
And I do have a mp4 video named "space" in my project files.
Any explanation to how or why this is happening and what actions I can take to fix this issue would be greatly appreciated.
I have checked and your code is running fine.
Just check that, the video should be under in your "Copy bundle Resources" of "Build Phases" tab in Target .
If it is not there , Please add the same as in below screenshots.
You are assigning non optional value of video. Make it optional, ? Swift means – It can have a value, but it can also be nil. It is defined by “?”.Please look below code
avPlayer = AVPlayer(url: video?)
if avPlayer != nil
{ // Video url is not empty
}
else
{ // It is empty
}

How To Make UISlider match Audio progress

I am creating a simple music app, and I was wondering how I can make a UiSlider to follow the progress of a audio file. Here's my project so far:
Code:
import UIKit
import AVFoundation
class SongDetailViewController: UITableViewController {
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
do {
audioPlayer = try AVAudioPlayer(contentsOf: URL.init(fileURLWithPath: Bundle.main.path(forResource: "Song Name", ofType: "mp3")!))
audioPlayer.prepareToPlay()
var audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSessionCategoryPlayback)
}
}
catch {
print(error)
}
}
// Buttons
// Dismiss
#IBAction func dismiss(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
// Play
#IBAction func play(_ sender: Any) {
audioPlayer.stop()
audioPlayer.play()
}
// Pause
#IBAction func pause(_ sender: Any) {
audioPlayer.pause()
}
// Restart
#IBAction func restart(_ sender: Any) {
audioPlayer.currentTime = 0
}
}
I'm wanting to create the uislider similar to the Apple Music app where it follows the audio file's progress and whenever the user slides the ball thing (lol) it goes to that time of the song. If you could give me some code to complete this, that would be amazing!
Please keep in mind that I am fairly new to coding and am still learning swift, so keep it simple :-) Thanks again!
If you switch to using an AVPlayer, you can add a periodicTimeObserver to your AVPlayer. In the example below you'll get a callback every 1/30 second…
let player = AVPlayer(url: Bundle.main.url(forResource: "Song Name", withExtension: "mp3")!)
player.addPeriodicTimeObserver(forInterval: CMTimeMake(1, 30), queue: .main) { time in
let fraction = CMTimeGetSeconds(time) / CMTimeGetSeconds(player.currentItem!.duration)
self.slider.value = fraction
}
Where you create an audioPlayer in your code, replace with the code above.
Using AVAudioPlayer you could create a periodic timer that fires several times a second (up to 60 times/second - any more would be a waste) and updates your slider based on your audio player's currentTime property. To sync the update with screen refresh you could use a CADisplayLink timer.
Edit:
This part of my answer doesn't work:
It should also be possible to set up a Key Value Observer on your
AVAudioPlayers currentTime property so that each time the value
changes your observer fires. (I haven't tried this, but it should
work.)

AVPlayer stuck video, but audio works

I go to create AVPlayerItem through AVURLAsset, my code:
let asset = AVURLAsset(URL: safeURL, options: [AVURLAssetPreferPreciseDurationAndTimingKey: true])
asset.loadValuesAsynchronouslyForKeys([assetKeyPlayable, self.assetKeyTracks, self.assetKeyHasProtectedContent]) {
() -> Void in
dispatch_async(dispatch_get_main_queue(), {
() -> Void in
// Use the AVAsset playable property to detect whether the asset can be played
if !asset.playable {
let localizedDescription = "Item cannot be played,Item cannot be played description"
let localizedFailureReason = "The assets tracks were loaded, but could not be made playable,Item cannot be played failure reason"
let userInfo = [NSLocalizedDescriptionKey: localizedDescription, NSLocalizedFailureReasonErrorKey: localizedFailureReason]
let error = NSError(domain: "domain", code: 0, userInfo: userInfo)
self.videoPlayerDelegate?.videoPlayer?(self, playerItemStatusDidFail: error)
self.cleanPlayer()
return
}
// At this point we're ready to set up for playback of the asset. Stop observing
if let _ = self.player?.currentItem {
self.cleanPlayer()
}
if asset.URL.absoluteString != safeURL.absoluteString {
return
}
var error: NSError?
let status = asset.statusOfValueForKey(self.assetKeyTracks, error: &error)
var playerItem = AVPlayerItem(URL: safeURL)
if status == .Loaded {
playerItem = AVPlayerItem(asset: asset)
} else {
// You should deal with the error appropriately.If Loaded fails, create an AVPlayerItem directly from the URL
playerItem = AVPlayerItem(URL: safeURL)
}
self.player = self.playerWithPlayerItem(playerItem)
self.registerMonitoring()
self.registerNotification()
self.addTimeObserver()
completionBlock?(loadURLString: playerURL.absoluteString)
})
}
Add AVPlayerLayer display video in my View, my code:
// MARK: - Property
var player: AVPlayer? {
get {
return playerLayer.player
}
set {
playerLayer.player = newValue
}
}
var playerLayer: AVPlayerLayer {
return layer as! AVPlayerLayer
}
When displaying video after completion of loading
self.videoPlayer?.loadPlayer({
[weak self](loadURLString) in
if let strongSelf = self {
strongSelf.player = strongSelf.videoPlayer?.player
strongSelf.startPlay()
}
})
Call seekToTime method to specify the play:
self.player?.currentItem?.seekToTime(CMTimeMakeWithSeconds(time, Int32(NSEC_PER_SEC)), toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero) {
[weak self] finished in
if let weakSelf = self {
if weakSelf.isPlaying {
weakSelf.videoPlayerDelegate?.videoPlayerDidplayerItemSeekToTime?(weakSelf)
}
}
}
Some pictures of the stuck interface:
In the first picture the sound is audible, but the interface is stuck.
In the second picture, the video works, but I get no sound.
My question:
When I call the seekToTime method upon completion, sometimes the video has sound, but the interface is stuck, occasionally the video works. I tried to call the CALayer setNeedsDisplay methods, to update the AVPlayerLayer picture, but that didn't help. I don't know what to do anymore, i would be grateful for every and any help.
Since this is happening for many of us in a different way and is not answered, I am answering it here, For me this was happening when I was trying to play the video in UIView using AVPlayer and AVPlayerLayer but when I used the same AVPlayer to load the video in AVPlayerViewController the video was getting stuck and audio kept on playing.
The trick is to destroy the AVPlayerLayer before using the same AVPlayer somewhere else ,Cant believe I found this enlightenment over here.
https://twitter.com/skumancer/status/294605708073263104
How I implemented this?
I created 2 view controllers in my main story board.
1) View Controller Scene
i) This has a UIView in it referenced to IBOutlet 'videoView'
ii) A button with segue navigation to 'show' AV Player View Controller Scene referenced to IBOutlet 'fullscreen'
2) AV Player View Controller Scene
// Code in your controller class.
// Make sure to reference your IBOutlet in your StoryBoard.
#IBOutlet weak var videoView : UIView!
#IBOutlet weak var fullscreen : UIButton!
var player:AVPlayer? = nil;
var playerLayer:AVPlayerLayer? = nil;
override func viewDidLoad() {
super.viewDidLoad()
// Your Url to play, could be hls type video
let url = URL(string: "https://your_url.m3u8");
let asset = AVAsset(url : url!)
let playerItem = AVPlayerItem(asset: asset)
player = AVPlayer(playerItem: playerItem)
}
override func viewDidAppear(_ animated: Bool) {
playInUIView()
pauseVideo()
}
// Creates An AVPlayerLayer and adds it as a sublayer in UIView.
func playInUIView(){
// Creating player layer to render video.
playerLayer = AVPlayerLayer(player: player)
playerLayer?.frame = self.videoView.bounds
playerLayer?.videoGravity = .resizeAspect
// Adding a sub layer to display it in UIView
self.videoView.layer.addSublayer(playerLayer!)
}
// Destroyes an AVPlayerLayer and Sublayer of your UIView
func destroyPlayInView(){
self.videoView.layer.sublayers=nil
playerLayer = nil
}
// On button click open AVPlayerViewController.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destination = segue.destination as! AVPlayerViewController
// Destroy AVPlayerLayer before rendering video in AVPlayerViewController.
destroyPlayInView()
// Set same AVPlayer in AVPlayerViewController.
destination.player = self.player
self.present(destination, animated: true) {
destination.player!.play()
}
}
PS - I have not implemented any controls as of yet and there could be coding malpractices as I have just started with swift and IOS development couple of days back, so please let me know wherever I am wrong.
Try this approach as I have encountered the same issue and solve it by this kind approach.
player.pause()
player.currentItem?.seek(to: CMTime(), completionHandler: { (_) in
player.play()
})
The same code of user nferocious76 but in Objective C
[self.player pause];
AVPlayerItem *playerItem = [self.player currentItem];
__weak typeof(self) weakSelf = self;
[playerItem seekToTime:playerItem.currentTime completionHandler:^(BOOL finished){
if (finished){
[weakSelf.player play];
}
}];

MPMoviePlayerViewController black screen after returning from background

I have a simple controller that plays a video when opened. This works, but when I put the app into the backround and return I get a blank or black screen. If I then navigate to another view and then return, the view is no longer black and the video is playing.
How can I have the video play upon returning from the background?
MyViewController:
var myMovieController : MPMoviePlayerViewController?
override func viewDidLoad() {
super.viewDidLoad()
self.initMovie()
}
func initMovie(){
let moviePath = NSBundle.mainBundle().pathForResource("my-movie", ofType: "mp4")
let movieURL = NSURL.fileURLWithPath(moviePath!)
self.myMovieController = MPMoviePlayerViewController(contentURL: movieURL)
self.myMovieController?.moviePlayer.view.frame = self.view.bounds
self.myMovieController?.moviePlayer.controlStyle = MPMovieControlStyle.None
self.myMovieController?.moviePlayer.prepareToPlay()
self.myMovieController?.moviePlayer.scalingMode = .AspectFill
self.myMovieController?.moviePlayer.fullscreen = true
self.myMovieController?.moviePlayer.repeatMode = MPMovieRepeatMode.One
self.view.insertSubview(myMovieController!.moviePlayer.view, atIndex:0)
self.myMovieController?.moviePlayer.play()
}
In applicationWillEnterForeground I got a reference to MyViewController and called play on myMovieController. Not sure if this is the correct way to go about it, but it seems to work.
func applicationWillEnterForeground(application: UIApplication) {
let mc: MyViewController = (window!.rootViewController as! MyViewController)
mc.myMovieController?.moviePlayer.prepareToPlay()
mc.myMovieController?.moviePlayer.play()
}

Resources