I have one countdown video to be played before quiz which is kept in bundle. The Video gets played properly in iPhone 6 [iOS 11.2] and quiz starts, but when I run the app in iPhone 4s [iOS 9.3] the video starts with a slight delay and at the end of video, the screen gets freeze for sometime and then the Quiz starts.
func playVideo()
{
guard let path = Bundle.main.path(forResource: "countDown", ofType:"mp4") else {
debugPrint("Video not found")
return
}
let item = AVPlayerItem(url: URL(fileURLWithPath: path))
player = AVPlayer.init(playerItem: item)
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = self.viewVideo.bounds
player?.volume = 0
self.viewVideo.isHidden = false
player?.play()
self.viewVideo.layer.addSublayer(playerLayer)
let resetPlayer = {
self.player?.pause()
self.viewVideo.isHidden = true
// Moves to quiz
self.performSegue(withIdentifier: "toFinalQuiz", sender: self)
}
playerObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: nil) {
notification in
resetPlayer()
}
}
Try adding this line after the comment,
// Moves to quiz
DispatchQueue.main.async {
self.performSegue(withIdentifier: "toFinalQuiz", sender: self)
}
Let me know if this doesn't work...
For the delay at the start, I'd recommend to keep the video loaded and ready to play beforehand. So that you can being playing the video using just player?.play() when you need it.
Related
I have an application that contains videos that play automatically in an UIImageView in a UITableView when the cell is visible, and all I am trying to do is allow the application to know when the video has been played for three seconds. I wrote this code.
class PostCell: UITableViewCell {
var player: AVPlayer?
var playerLayer: AVPlayerLayer?
var post: Post? {
didSet {
updateView()
}
}
func updateView() {
self.viewcount()
if let videoUrlString = post?.videoUrl, let videoUrl = URL(string: videoUrlString) {
player = AVPlayer(url: videoUrl)
playerLayer = AVPlayerLayer(player: player)
playerLayer?.frame = postImageView.frame
playerLayer?.frame.size.width = postImageView.frame.size.width
playerLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
self.contentView.layer.addSublayer(playerLayer!)
player?.play()
}
func viewcount() {
if let currentitem = player?.currentItem {
if currentitem.currentTime() == CMTimeMake(3, 1) {
print ("VIDEO PLAYED FOR THREE SECONDS")
}
}
}
}
but it is not printing out my message once the video starts playing. I have searched the web for help but couldn't find anything on this subject. So could anyone please help with my issue and tell me what I am doing wrong ?
You are searching for observer of player here is how you can check and track the current position of AVPlayer
Here is function that is adding observer to cell
private func addObserversForVideoPlayer(cell:CustomCell) {
let observer = cell.player?.addPeriodicTimeObserver(forInterval: CMTime.init(seconds: 1, preferredTimescale: 1), queue: .main, using: {[weak self,weak cell] (time) in
guard let cell = cell else {return}
if cell.player?.currentItem?.status == .readyToPlay {
// print("Inside Will DISPLAY\(cell.video.currentTime)")
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())
}
})
NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: cell.player?.currentItem, queue: .main, using: {[weak cell,weak self] (notification) in
if cell?.player != nil {
cell?.player?.seek(to: kCMTimeZero)
cell?.player?.play()
}
})
}
so that addPeriodicTimeObserver will notify you when the player start playing.
And NSNotification.Name.AVPlayerItemDidPlayToEndTime will notify you when your AVPlayer stops.
Note1: If your cell.player?.currentItem is nil while you are adding AVPlayerItemDidPlayToEndTime it will be cause bug see this One AVPlayer's AVPlayerItemDidPlayToEndTime action executed for all Currently playing videos , If . you don't need it don't add it :)
Note2: You should keep observer so after time you can remove it so that can not take extra load on memory
Hope it is helpful
Try calling the view count after player had started playing
func updateView() {
/// Not here Because at this time player current item is not initiated yet
/// if you use Breakpoints in viewCount code you will see it won't enter
/// in if condition created
self.viewcount() /// Comment this line
if let videoUrlString = post?.videoUrl, let videoUrl = URL(string: videoUrlString) {
player = AVPlayer(url: videoUrl)
playerLayer = AVPlayerLayer(player: player)
playerLayer?.frame = postImageView.frame
playerLayer?.frame.size.width = postImageView.frame.size.width
playerLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
self.contentView.layer.addSublayer(playerLayer!)
/// Player is initiated with a item to play
player?.play()
/// Call current time here
/// Now it will Enter in if Condition
/// Also try using else statement so you know Do control enter in if or in Else
self.viewcount()
}
func viewcount()
{
if let currentitem = player?.currentItem
{
///Yes Player have a item whose time can be Detected
if currentitem.currentTime() == CMTimeMake(3, 1)
{
print ("VIDEO PLAYED FOR THREE SECONDS")
}
}
else
{
/// Check do Control reach here in case 1 When you are calling before player.play()
}
}
I have an app where I play a video in the background in a certain view.
This video is always supposed to play fullscreen. I recently asked a question about stretching this video to fill up an iPhone plus model (make AVPlayer fill the entire screen) However, I just tested on an iPhone X, and the following happens:
I made the background of the view containing the AVPlayerLayer purple, just to clarify. As you can see, the video plays in the top of the screen (appx. 80pt), then there's the complete video, and then there's nothingness. How is this possible if my view does actually cover the entire screen?
This is how the video is made:
#objc fileprivate func playVideo() {
log.debug("playVideo")
let file = self.videoName.components(separatedBy: ".")
guard let path = Bundle.main.path(forResource: file[0], ofType:file[1]) else {
debugPrint( "\(file.joined(separator: ".")) not found")
return
}
self.player = AVPlayer(url: URL(fileURLWithPath: path))
let playerLayer = AVPlayerLayer(player: self.player)
playerLayer.frame = self.bounds
self.layer.addSublayer(playerLayer)
self.play()
if (self.loop) {
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.player.currentItem, queue: .main) { _ in
self.restart()
}
}
self.backgroundColor = .purple
NotificationCenter.default.addObserver(
self,
selector: #selector(self.restart),
name: Notification.Name("playVideos"),
object: nil)
}
This method is called after layoutSubviews().
How do I make the video stretch fully (with some overflow on the sides to make it visible of course)? Thanks :-).
I am trying to build an iOS app which plays a sound. there is a button for On and Off. the button is working fine when we are inside the app. However, it is not working the way it needs to work if we r in the background.
Say the sound is in the OFF state, we r outside the app (app is still in the background), now if we open any other app and return to my app, the sound is supposed to be in OFF state, but it is turned ON. Same happens for the other state too (ON to OFF).
Here is the code I am using.
func SetUpSound() {
if let path = Bundle.main.path(forResource: "firenew", ofType: "mp3") {
let filePath = NSURL(fileURLWithPath:path)
player = try! AVAudioPlayer.init(contentsOf: filePath as URL)
player.numberOfLoops = -1 //logic for infinite loop
player.prepareToPlay()
player.play()
}
let audioSession = AVAudioSession.sharedInstance()
try!audioSession.setCategory(AVAudioSessionCategoryPlayback, with: AVAudioSessionCategoryOptions.duckOthers)
}
The below code is for ON and OFF.
#IBAction func firePressed(_ sender: UIButton) {
if fireOnoff.isHidden == true {
fireOnoff.isHidden = false
infoPopup.isHidden = true
popViewslider.isHidden = true
videoContolpopup.isHidden = true
volumePopup.isHidden = true
}
if player.isPlaying {
player.pause()
onoffLabel.textAlignment = .center
onoffLabel.text = "Off"
} else {
player.numberOfLoops = -1
player.play()
onoffLabel.textAlignment = .center
onoffLabel.text = "On"
}
toggleButton(button: sender, onImage: #imageLiteral(resourceName: "selectedfire "), offImage: #imageLiteral(resourceName: "unselectedfire"))
}
I am building for iOS and tvOS. Both face same problem.
Do you have any suggestions for resolving this? Thanks in advance!!
Edit: I have already turned on the Audio, AirPlay and Picture in Picture in Background Modes capabilities.
I am writing an app that need display different video according to the selection of the user. When the user select a video, the function playVideo will be called. And after the video finish playing, then the videoView will be hidden again.
My code is as follows:
var player: AVPlayer?
func playVideo(String: videoFile) {
self.videoView.isHidden = false
let videoURL: NSURL = Bundle.main.url(forResource: videoFile, withExtension: "mp4")! as NSURL
self.player = AVPlayer(url: videoURL as URL)
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = self.videoView.frame
self.videoView.layer.addSublayer(playerLayer)
let duration : Int64 = 0
let preferredTimeScale : Int32 = 1
let seekTime : CMTime = CMTimeMake(duration, preferredTimeScale)
self.player?.seek(to: seekTime)
self.player?.play()
NotificationCenter.default.addObserver(self, selector: #selector(self.playerItemDidReachEnd), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player?.currentItem)
}
#objc func playerItemDidReachEnd()
{
self.player?.pause()
self.videoView.isHidden = true
NotificationCenter.default.removeObserver(self)
}
However, with the code above, i have several question:
How to delete / deallocate the player gracefully? If just using my current code, will it consume lots of memory?
Every time, when the user press a button, the function playVideo will be called, and the corresponding player will be created and play. Is this the right way to do so? Is there any other method or more efficient way or elegant way to do so?
I did try to replace the code on creation of the player by the following, but it fails to play the video.
let playerItem: AVPlayerItem = AVPlayerItem(url: videoURL as URL)
self.player? = AVPlayer(playerItem: playerItem)
Thank you
I just switched from AVAudioPlayer to AVPlayer and I'm going through my old functions and making the appropriate adjustments. The AVPlayer is being used to play remote audio files using URL's. When I select a file to play and pause everything works perfectly. However, when I want to resume playing the paused file, the player won't play even though I see the play function is being called. I set up an observer to know when the player is done playing so the play/pause button can toggle and what I noticed is that the observer is getting called after I hit pause. This shouldn't happen since the player isn't done playing right? Anyways, I set breakpoints and everything is getting called correctly. Any idea why the AVPlayer won't resume playing after being paused?
var playerItem: AVPlayerItem?
var newPlayer: AVPlayer?
var trackIDplaying: Int?
func playPausePressed(_ sender:UIButton)
{
if let selectedTrackID = trackIDplaying
{
do
{
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
if selectedTrackID == track.trackId
{
//if playing, then pause. If not playing, then play
if (self.newPlayer!.rate != 0)
{
self.newPlayer!.pause()
self.isPaused = true
}
else
{
if self.newPlayer!.currentItem != nil
{
self.newPlayer!.play()
print(self.newPlayer!.currentItem)
}
}
}
else
{
//If song is playing, switch to new song
let trackURL = URL(string: track.preSignedURL!)
trackIDplaying = track.trackId
self.playerItem = AVPlayerItem(url: trackURL!)
NotificationCenter.default.addObserver(self, selector: #selector((HomeController.playerDidFinishPlaying)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: self.playerItem)
self.newPlayer = AVPlayer(playerItem: self.playerItem!)
self.newPlayer!.play()
}
}
catch let error1 as NSError
{
error = error1
self.newPlayer = nil
}
}
else
{
do
{
//play selected song if no other songs are playing
let trackURL = URL(string: track.preSignedURL!)
print(trackURL!)
self.playerItem = AVPlayerItem(url: trackURL!)
NotificationCenter.default.addObserver(self, selector: #selector((HomeController.playerDidFinishPlaying)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: self.playerItem)
self.newPlayer = AVPlayer(playerItem: self.playerItem!)
self.newPlayer!.play()
self.trackIDplaying = track.trackId
}
}
if let err = error
{
print("audio player error \(err.localizedDescription)", terminator: "")
}
}
func playerDidFinishPlaying(note: NSNotification) {
guard let indexPath = self.playingAudioIndexPath, let cell = self.audioTable.cellForRow(at: indexPath) as? AudioCell else {
return;
}
cell.playButton.isSelected = false
self.playingAudioIndexPath = nil
}
try adding observer in only viewDidLoad() or viewWillAppear() adding observer should only done in one time for these kind of problems.
Still you have any problem feel free to ask me.