I have a simple camera that records a video then transitions to a videoPreview. In it there is a button that hwne presses segues back to the camera. However hwen I do this the video audio does not stop. How can I delete it (not mute) so that a new vid can be taken?
Bellow is my Video preview code with what I have attempted
class VideoPlayback: UIViewController {
//receives url video value from VideoViewController
var videoURL: URL!
//place where video gets displayed
#IBOutlet weak var videoView: UIView!
var avPlayer = AVPlayer()
private var playerQueue: AVQueuePlayer!
private var playerItem1: AVPlayerItem!
private var playerLooper: AVPlayerLooper!
private var playerLayer: AVPlayerLayer!
override func viewDidLoad() {
super.viewDidLoad()
print(avPlayer, "avPlayer")
print("videoURL", videoURL)
playerItem1 = AVPlayerItem(url: videoURL as URL)
print(playerItem1, "playerItem1")
playerQueue = AVQueuePlayer(playerItem: playerItem1)
print(playerQueue, "player queue")
playerLayer = AVPlayerLayer(player: playerQueue)
print(playerLayer, "playerLayer")
playerLooper = AVPlayerLooper(player: playerQueue, templateItem: playerItem1)
print(playerLooper, "player looper")
playerLayer.frame = view.bounds
playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
print(videoView, "yruewioq")
videoView.layer.insertSublayer(playerLayer, at: 0)
view.layoutIfNeeded()
self.playerQueue?.play()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//attempt at turing audio off
playerQueue = nil
}
#IBAction func cancelVidPreviewButton(_ sender: Any) {
performSegue(withIdentifier: "cancelVideo", sender: self)
}
}
Update:
I have also tried playerLayer.isMuted = true
But I get that it has no member isMuted
If I understand correctly you need to make sure audio session has ended before segue. Setting volume to zero might affect audio session though.
You can use the AVPlayer's volume property to control the volume of AVPlayer.
Setting volume to 0 can mute the audio.
According to the Documentation:
A value of 0.0 indicates silence; a value of 1.0 (the default)
indicates full audio volume for the player instance.
Hope this helps
So I see that actually what needs to be done here is all the video data which has audio needs to be removed as seen bellow
self.playerQueue?.removeAllItems()
But if you want to get rid of audio you just need to do .pause() and then remove the data. here is docs
Related
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
}
I have a tvOS app which has a video playing in it.
There are basically two videos (different speed versions of the same video). One is 12MB in size and another is 1.9MB.
When the app starts, it runs fine (Xcode shows 191MB). However, when clicking normal speed button once, the memory shoots to 350MB. As and when I click normal and fast buttons respectively, this goes on increasing and at one point it becomes 1GB+. You can see the attachment. It even went to 3GB when the video stuttered and the app stopped.
Is there any way to solve the memory issue and save the app from stopping?
Another problem is: when in Apple TV, we go to another app from this app and come back, the video again stops. However, in Simulator, it is not happening. Can someone help me to solve these two issues?
Here is the code I am using:
var avPlayerLayer: AVPlayerLayer!
var paused: Bool = false
func playmyVideo(myString: String) {
let bundle: Bundle = Bundle.main
let videoPlayer: String = bundle.path(forResource: myString, ofType: "mov")!
let movieUrl : NSURL = NSURL.fileURL(withPath: videoPlayer) as NSURL
print(movieUrl)
viewVideo.playVideoWithURL(url: movieUrl)
}
#IBAction func normalPressed(_ sender: Any) {
playmyVideo(myString: "normal")
}
#IBAction func forwardPressed(_ sender: Any) {
playmyVideo(myString: "fast")
}
class VideoPlay: UIView {
private var player : AVPlayer!
private var playerLayer : AVPlayerLayer!
init() {
super.init(frame: CGRect.zero)
self.initializePlayerLayer()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.initializePlayerLayer()
self.autoresizesSubviews = false
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.initializePlayerLayer()
}
private func initializePlayerLayer() {
playerLayer = AVPlayerLayer()
playerLayer.backgroundColor = UIColor.clear.cgColor
playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
self.layer.addSublayer(playerLayer)
playerLayer.frame = UIScreen.main.bounds
}
func playVideoWithURL(url: NSURL) {
player = AVPlayer(url: url as URL)
player.isMuted = false
playerLayer.player = player
player.play()
loopVideo(videoPlayer: player)
}
func toggleMute() {
player.isMuted = !player.isMuted
}
func isMuted() -> Bool
{
return player.isMuted
}
func loopVideo(videoPlayer: AVPlayer) {
NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil, queue: nil) { notification in
let t1 = CMTimeMake(5, 100);
self.player.seek(to: t1)
videoPlayer.seek(to: kCMTimeZero)
self.player.play()
}
}
}
I see two problems in your code:
Each time playVideoWithURL method is called, you create new AVPlayer instance, instead of reusing already existing one. You can call replaceCurrentItem(with:) method on your player property when you want to play another URL.
That itself is a bit inefficient, but shouldn't cause the memory issue you described. I think the reason is:
Each time loopVideo method is called, you pass a closure to NotificationCenter.default.addObserver. This closure creates a strong reference to videoPlayer. You never remove the observer from the notification center.
As loopVideo is called each time you create new AVPlayer instance, these instances are never deallocated, leading to the memory issue you described.
To fix it, you can:
initialize player property only once in playVideoWithURL, then use replaceCurrentItem when you want to play another video
also change the "loop" logic, so that you call NotificationCenter.default.addObserver only once
the closure you pass to NotificationCenter.default.addObserver creates a memory leak (see this question). You can get rid of it by capturing self weakly:
NotificationCenter.default.addObserver(forName:
NSNotification.Name.AVPlayerItemDidPlayToEndTime,object: nil, queue: nil) { [weak self], notification in
self?.player.seek(to: kCMTimeZero)
self?.player.play()
}
also remember to call removeObserver in deinit method of VideoPlay class.
Some days ago I was asked to check how difficult is to play a video while downloading it from Internet. I know it's an easy task because someone told me a while ago. So, I checked and it was super easy.
The problem was that I wanted to save to disk the video to do not force the user to download it again and again.
The problem was to access the buffer and store it to disk.
Many answers in Stackoverflow says it is nor possible. Specially with videos.
My original code to play the video:
import AVFoundation
....
//MARK: - Accessors
lazy var player: AVPlayer = {
var player: AVPlayer = AVPlayer(playerItem: self.playerItem)
player.actionAtItemEnd = AVPlayerActionAtItemEnd.None
return player
}()
lazy var playerItem: AVPlayerItem = {
var playerItem: AVPlayerItem = AVPlayerItem(asset: self.asset)
return playerItem
}()
lazy var asset: AVURLAsset = {
var asset: AVURLAsset = AVURLAsset(URL: self.url)
return asset
}()
lazy var playerLayer: AVPlayerLayer = {
var playerLayer: AVPlayerLayer = AVPlayerLayer(player: self.player)
playerLayer.frame = UIScreen.mainScreen().bounds
playerLayer.backgroundColor = UIColor.clearColor().CGColor
return playerLayer
}()
var url: NSURL = {
var url = NSURL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")
return url!
}()
//MARK: - ViewLifeCycle
override func viewDidLoad() {
super.viewDidLoad()
view.layer.addSublayer(playerLayer)
player.play()
}
The solution for this problem is to use AVAssetExportSession and AVAssetResourceLoaderDelegate:
First step is to add a notification to know when the video finish. Then we can start saving it to disk.
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(playerItemDidReachEnd(_:)), name: AVPlayerItemDidPlayToEndTimeNotification, object: nil)
...
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
The implementation of our function:
func playerItemDidReachEnd(notification: NSNotification) {
if notification.object as? AVPlayerItem == player.currentItem {
let exporter = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality)
let filename = "filename.mp4"
let documentsDirectory = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).last!
let outputURL = documentsDirectory.URLByAppendingPathComponent(filename)
exporter?.outputURL = outputURL
exporter?.outputFileType = AVFileTypeMPEG4
exporter?.exportAsynchronouslyWithCompletionHandler({
print(exporter?.status.rawValue)
print(exporter?.error)
})
}
}
Finally we need to make our AVURLAsset delegate of AVAssetResourceLoaderDelegate:
lazy var asset: AVURLAsset = {
var asset: AVURLAsset = AVURLAsset(URL: self.url)
asset.resourceLoader.setDelegate(self, queue: dispatch_get_main_queue())
return asset
}()
And:
extension ViewController : AVAssetResourceLoaderDelegate {
}
I created a small demo with this code in GitHub.
The team at Calm has open-sourced our implementation to this. It's available as a CocoaPod. It's called PersistentStreamPlayer.
Features include:
streaming of audio file, starting playback as soon as first data is available
also saves streamed data to a file URL as soon as the buffer completes
exposes timeBuffered, helpful for displaying buffer progress bars in the UI
handles re-starting the audio file after the buffer stream stalls (e.g. slow network)
simple play, pause and destroy methods (destroy clears all memory resources)
does not keep audio file data in memory, so that it supports large files that don't fit in RAM
You can find it here: https://github.com/calmcom/PersistentStreamPlayer
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];
}
}];
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