How to play again the same audio after finished? Now I use this code with AVPlayer:
fileprivate func play () {
guard let url = URL(string: record.recordUrl) else { return }
let playItem = AVPlayerItem(url: url)
player.replaceCurrentItem(with: playItem)
NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: playItem)
player.play()
}
#objc func playerDidFinishPlaying(note: NSNotification) {
print("Player finished")
currentTimeSlider.setValue(0, animated: true)
currentTimeLabel.text = "00:00"
}
#objc func playerDidFinishPlaying(note: NSNotification) {
print("Player finished")
currentTimeSlider.setValue(0, animated: true)
currentTimeLabel.text = "00:00"
//Swift 4
player.seek(to: .zero, toleranceBefore: .zero, toleranceAfter: .zero)
//Swift 2 & 3 (I believe for 2 & 3)
//player.seek(to: kCMTimeZero, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero)
//Play
player.play()
}
I added the seek back to the beginning (the tolerance makes sure it is exactly at zero, and not around zero with some margin of error).
Also added the feature to play when done.
Related
We're trying to implement AVPlayer seek in our SwiftUI app, it worked prior to iOS 15.4 but not after the update.
let playerCurrentTime = CMTimeGetSeconds(player.currentTime())
let newTime = playerCurrentTime + 45
let time2: CMTime = CMTimeMake(value: Int64(newTime * 1000 as Float64), timescale: 1000)
player.seek(to: time2, toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero) { success in
print(success)
}
The completionHandler is called immediately with success = false.
No other seek operations are running, and the AVPlayer status is readyToPlay.
We're streaming a MP3 file from an URL, using this initialisation code:
playerItem = AVPlayerItem(url: url)
player = AVPlayer(playerItem: playerItem)
player.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 2), queue: DispatchQueue.main) { _ in
if self.player.currentItem?.status == .readyToPlay {
self.currentTimeInSeconds = CMTimeGetSeconds(self.player.currentTime())
self.progressInPct = Double(self.currentTimeInSeconds) / Double(self.totalTimeInSeconds)
self.nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = 1
MPNowPlayingInfoCenter.default().nowPlayingInfo = self.nowPlayingInfo
self.setupNowPlaying()
} else {
self.nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = 0
MPNowPlayingInfoCenter.default().nowPlayingInfo = self.nowPlayingInfo
}
}
We tried seeking on currentItem as well, but that didn't work either.
player.currentItem?.seek(to: time, toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero)
Anyone else experienced something like this, and have any pointers?
UPDATE:
Tried doing a complete bare bones attempt, but still the same result:
struct testView: View {
var player = AVPlayer()
var body: some View {
Button {
self.startPlayer(url: episode.streamUrl!)
}
label: {
Text("Test")
}
}
func startPlayer(url: String) {
let playerItem = AVPlayerItem(url: URL(string: url) !)
self.player.replaceCurrentItem(with: playerItem)
player.play()
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
let time2: CMTime = CMTimeMake(value: Int64(45 * 1000 as Float64), timescale: 1000)
player.seek(to: time2, toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero) {
success in
print(success)
}
}
}
}
Prints "false".
For comparision here I attach piece of code that I use for time observation and 3 types of seeking functions. It is working fine so far on iOS 15.4, (although on UIKit and with .m3u8 playlist). Hopefully it might help you in some way.
seeking functions:
func seek1() {
let videoDuration = (avPlayer.currentItem?.duration.seconds ?? 0)!
let elapsedTime: Float64 = videoDuration * Float64(archiveControlsView.seekSlider.value)
let toTime = CMTime.init(seconds: elapsedTime,
preferredTimescale: 100)
avPlayer.seek(to: toTime,completionHandler: { (completed: Bool) -> Void in
//do whatever you need
})
}
func seek2() {
let diff: TimeInterval = 60
self.avPlayer.seek(to: CMTime.init(seconds: diff, preferredTimescale: 100))
}
func seekToZero() {
self.avPlayer.seek(to: kCMTimeZero)
}
time observing function
var timeObserver: AnyObject!
func createTimer() {
let timeInterval: CMTime = CMTime.init(seconds: 1.0, preferredTimescale: 10)
timeObserver = avPlayer.addPeriodicTimeObserver(forInterval: timeInterval,
queue: DispatchQueue.main) {
(elapsedTime: CMTime) -> Void in
guard self.status != .Seeking else { return }
self.observeTime(elapsedTime)
let duration = (self.avPlayer.currentItem?.duration.seconds ?? 0)!
let elapsedTime = elapsedTime.seconds
// do whatever you need with elapsed and duration
} as AnyObject?
}
I am trying to get a video background in my app and I have written the following code. It runs one time and then stops. How do I make the video repeat forever? Thanks in advance.
#IBOutlet weak var videoLayer: UIView!
override func viewDidLoad() {
super.viewDidLoad()
playBackgroundVideo()
}
func playBackgroundVideo(){
guard let path = Bundle.main.path(forResource: "City", ofType: "MOV") else {
return
}
let player = AVPlayer(url: URL(fileURLWithPath: path))
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = self.view.bounds
playerLayer.videoGravity = .resizeAspectFill
self.videoLayer.layer.addSublayer(playerLayer)
player.play()
}
#objc func playerItemDidReachEnd(notification: Notification) {
let p: AVPlayerItem = notification.object as! AVPlayerItem
p.seek(to: .zero)
}
}
Try adding a notification listener to trigger the playerItemDidReachEnd code.
You do this by involving a NotificationCenter.default.addObserver in your setup.
Did not test your code, but you want a setup like this example:
#IBOutlet weak var videoLayer: UIView!
var player: AVPlayer!
override func viewDidLoad()
{
super.viewDidLoad()
playBackgroundVideo()
}
func playBackgroundVideo()
{
guard let path = Bundle.main.path(forResource: "City", ofType: "MOV") else{ return }
player = AVPlayer(url: URL(fileURLWithPath: path))
let playerLayer = AVPlayerLayer(player: player)
//# add a Listener
NotificationCenter.default.addObserver( self,
selector: #selector(playerItemDidReachEnd),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
object: nil)
playerLayer.frame = self.view.bounds
playerLayer.videoGravity = .resizeAspectFill
self.videoLayer.layer.addSublayer(playerLayer)
player.play()
}
#objc func playerItemDidReachEnd(notification: Notification)
{
player.seek(to: CMTime.zero)
player.play()
}
i have black screen after video is over, but i would like to be redirect to another storyboard when video is over.
Could somebody help me with this.
Thanks you
import UIKit
import AVKit
import AVFoundation
class ViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
playVideo()
}
private func playVideo() {
guard let path = Bundle.main.path(forResource: "Kristinka", ofType:"m4v") else {
debugPrint("video.m4v not found")
return
}
let player = AVPlayer(url: URL(fileURLWithPath: path))
let playerController = AVPlayerViewController()
playerController.showsPlaybackControls = false
playerController.player = player
present(playerController, animated: true) {
player.play()
}
}
}
You need to detect when the item you're playing has reached the end. To do that, you can add an observer. For example:
func finishedVideo(_ notification: NSNotification) {
print("Animation did finish")
}
private func playVideo() {
guard let path = Bundle.main.path(forResource: "Kristinka", ofType:"m4v") else {
debugPrint("video.m4v not found")
return
}
let player = AVPlayer(url: URL(fileURLWithPath: path))
let playerController = AVPlayerViewController()
playerController.showsPlaybackControls = false
playerController.player = player
present(playerController, animated: true) {
NotificationCenter.default.addObserver(self,
selector: #selector(finishedVideo(_:)),
name: .AVPlayerItemDidPlayToEndTime,
object: player?.currentItem)
player.play()
}
}
And I dug this code up on this very related question. There's a lot of related/duplicate answers if you do a search.
AVPlayer is working fine in simulator but crashes on device.totally stuck please help out.
override func viewDidAppear(animated: Bool) {
let fileURL = NSURL(string: NSUserDefaults.standardUserDefaults().objectForKey("introVideoURl") as! String)
playVideo(fileURL!) // crashing line
}
func playVideo(url: NSURL) throws-> AnyObject{
let player = AVPlayer(URL: url)
playerController.player = player
self.addChildViewController(playerController)
self.view.addSubview(playerController.view)
playerController.view.frame = self.view.frame
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(introVideoController.playerDidFinishPlaying(_:)),
name: AVPlayerItemDidPlayToEndTimeNotification, object: player.currentItem)
player.play()
return true
}
I want to add the following method in for loop.
func playQuequeSounds(time:String){
let song1 = AVPlayerItem(URL: NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("estimatedTime", ofType: "mp3", inDirectory: "Audiofiles")!))
let song2 = AVPlayerItem(URL: NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(time, ofType: "mp3", inDirectory: "Audiofiles")!))
let song3 = AVPlayerItem(URL: NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("minutes", ofType: "mp3", inDirectory: "Audiofiles")!))
self.queueplayer = AVQueuePlayer(items: [song1,song2,song3])
self.queueplayer.actionAtItemEnd = AVPlayerActionAtItemEnd.Advance
self.queueplayer.play()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "playerDidFinishPlaying:", name: AVPlayerItemDidPlayToEndTimeNotification , object: song3)
}
i add the above method in viewWillApear
override func viewWillAppear(animated: Bool) {
voiceRouteTableView.reloadData()
NSNotificationCenter.defaultCenter().removeObserver(self)
for i in 0...voicesRoutes.count{
flag=false
self.playQuequeSounds(String(i+1))
while flag==false{
}
}
sleep(2)
}
The problem is that the loop runs very quicky and doesn't wait the avqueueplayer to finish the play. Add notification and change the code into the loop for waiting until the follow method call but the same problem.
func playerDidFinishPlaying(note: NSNotification) {
flag=true
}