AVPlayer play with update label cause an issue - ios

I have an issue with AVPlayer play and same time updates label.
I have timer that updates label with progress of player, but that stuck my video at same position...check below image..After button click timer starts and cause an issue..
Code:
func playVideo() {
let path = NSBundle.mainBundle().pathForResource("videoname", ofType:"mp4")
let fileURL = NSURL(fileURLWithPath: path!)
moviePlayer = AVPlayer(URL: fileURL)
// Play the video
moviePlayer!.play()
}
#IBAction func progresss(sender: UIButton) {
self.meterTimer = NSTimer.scheduledTimerWithTimeInterval(0.1,
target:self,
selector:"updateAudioMeter:",
userInfo:nil,
repeats:true)
}
func updateAudioMeter(timer:NSTimer) {
lbltime.text = "\(CMTimeGetSeconds(moviePlayer!.currentItem.currentTime()))"
}
Sample project link
Source code
Note:please add video in bundle if you are use sample project.I just delete video because of file size
Question
How to avoid this? and why this happens because when I used MPMovieController it works great.

From your sample code the problem is below function:
override func viewDidLayoutSubviews() {
let layer = AVPlayerLayer(player: moviePlayer)
layer.frame = self.viewPlayer.frame
self.viewPlayer.layer.addSublayer(layer)
}
This will resize your layer view every time when your label update so just remove and add that code into playVideo method and your method will be:
func playVideo() {
let path = NSBundle.mainBundle().pathForResource("video", ofType: "MOV")
let url = NSURL.fileURLWithPath(path!)
moviePlayer = AVPlayer(URL: url)
moviePlayer!.allowsExternalPlayback = false
var introPlayerLayer = AVPlayerLayer(player: moviePlayer)
viewPlayer.layer.addSublayer(introPlayerLayer)
introPlayerLayer.frame = viewPlayer.bounds
moviePlayer!.play()
}
And call this method in viewDidAppear instead of viewDidLoad method and your complete code will be:
import UIKit
import MediaPlayer
import AVFoundation
class ViewController: UIViewController {
var moviePlayer : AVPlayer?
var meterTimer : NSTimer!
#IBOutlet weak var viewPlayer: UIView!
#IBOutlet weak var lbltime: UILabel!
override func viewDidAppear(animated: Bool) {
playVideo()
}
func playVideo() {
let path = NSBundle.mainBundle().pathForResource("video", ofType: "MOV")
let url = NSURL.fileURLWithPath(path!)
moviePlayer = AVPlayer(URL: url)
moviePlayer!.allowsExternalPlayback = false
var introPlayerLayer = AVPlayerLayer(player: moviePlayer)
viewPlayer.layer.addSublayer(introPlayerLayer)
introPlayerLayer.frame = viewPlayer.bounds
moviePlayer!.play()
}
#IBAction func progresss(sender: UIButton) {
moviePlayer?.seekToTime(kCMTimeZero)
moviePlayer?.addPeriodicTimeObserverForInterval(CMTimeMakeWithSeconds(1, 1), queue: nil, usingBlock: {
(CMTime) -> Void in
self.updateProgressBar()
})
}
func updateProgressBar(){
var timeNow = Int(self.moviePlayer!.currentTime().value) / Int(self.moviePlayer!.currentTime().timescale)
var currentMins = timeNow / 60
var currentSec = timeNow % 60
var duration: String = "\(currentMins):\(currentSec)"
self.lbltime.text = duration
}
}

I have written a sample code for Objective-C, you can convert same in swift for your project accordingly.
// Implement following code in AVPlayer observer method for `AVPlayerStatusReadyToPlay` state
[avPlayer addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(interval, NSEC_PER_SEC)
queue:NULL
usingBlock:
^(CMTime time)
{
// update label value here, it keeps invoking periodically internally.
CMTime playerDuration = [self playerItemDuration];
}];
// Method to get Player current time
- (CMTime)playerItemDuration
{
AVPlayerItem *thePlayerItem = [avPlayer currentItem];
if (thePlayerItem.status == AVPlayerItemStatusReadyToPlay)
{
return([avPlayerItem duration]);
}
return(kCMTimeInvalid);
}
if you simply want to update it then invoke playerItemDuration to get current time, but make it asynchronous process.

Related

AVPlayer keeps adding streams instead of replacing

I am triggering a video play from a URL. It's just a button, opening the AVPlayerController full screen. If people close it, the can go back to the another item, with possibly a video. There they can click that video to start, however, when they do so, I can hear the audio of the previous player, of the other VC playing in the together with this one. This keeps layering up. How can I avoid this?
This is my class for the videoplayer
import UIKit
import AVFoundation
import AVKit
class simpleVideoPlayer: UIViewController {
var playerController = AVPlayerViewController()
var player:AVPlayer?
var inputVideoUrl: String? = nil
func setupVideo() {
self.player = AVPlayer()
self.playerController.player = self.player
}
func playNext(url: URL) {
let playerItem = AVPlayerItem.init(url: url)
self.playerController.player?.replaceCurrentItem(with: playerItem)
self.playerController.player?.play()
}
func setupVideoUrl(url: String) {
inputVideoUrl = url
}
}
This is in my viewcontroller. It's first getting a URL of a possible advert from my server, if that failed, then it wil just load the "default" video.
let SimpleVideo = simpleVideoPlayer()
#objc func handleTap(gestureRecognizer: UIGestureRecognizer)
{
ApiVideoAdvertService.sharedInstance.fetchVideoAdvert { (completion: VideoAdvert) in
let advertUrl = URL(string: completion.video_adverts_url)
var url = URL(string: (self.article?.video_link?.files[0].link_secure)!)
var showAdvert: Bool = false
if (advertUrl != nil && UIApplication.shared.canOpenURL(advertUrl!)) {
url = advertUrl
showAdvert = true
}
let appDelegate = UIApplication.shared.delegate as! AppDelegate
if (showAdvert) {
NotificationCenter.default.addObserver(self, selector: #selector(self.finishVideo),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: self.SimpleVideo.playerController.player?.currentItem)
}
appDelegate.window?.rootViewController?.present(self.SimpleVideo.playerController, animated: true, completion: {
self.SimpleVideo.setupVideo()
if (showAdvert) {
self.SimpleVideo.playerController.setValue(true, forKey: "requiresLinearPlayback")
}
self.SimpleVideo.playNext(url: url!)
})
}
#objc func finishVideo() {
let url = URL(string: (article?.video_link?.files[0].link_secure)!)
SimpleVideo.playerController.setValue(false, forKey: "requiresLinearPlayback")
SimpleVideo.playNext(url: url!)
}
Removing the observer inside finishVideo did it.
#objc func finishVideo() {
NotificationCenter.default.removeObserver(self)
let url = URL(string: (article?.video_link?.files[0].link_secure)!)
SimpleVideo.playerController.setValue(false, forKey: "requiresLinearPlayback")
SimpleVideo.playNext(url: url!)
}

Swift 3, XCode 8 - Changing Local Video on Button Press - AVPlayer

I'm trying to create an app that will change videos on each button press.
Currently: The video loops and the button does nothing
Goals: The button will cycle to the next video based on the array index. I plan on associating another array with text descriptions to go along with each video.
Thoughts: I think a JSON based implementation would probably work better. This is my first iOS app and I'm trying to keep it simple. Thoughts and help are greatly appreciated!
Here is the function that loops the video. The name of name of the video is held in an array called videoId:
class WorkoutViewController: UIViewController{
#IBOutlet weak var videoView: UIView!
#IBOutlet weak var infoCardView: UIView!
var currentVidIndex: Int = 0
var player: AVPlayer?
var playerLayer: AVPlayerLayer?
override func viewDidLoad() {
super.viewDidLoad()
shadowBackground()
}
var videoId: [String] = ["bodyweight_fitness_arch","bodyweight_fitness_assisted_squat","bodyweight_fitness_band_dislocates"]
func setLoopingVideo(){
let path = Bundle.main.path(forResource: videoId[currentVidIndex], ofType: "mp4")
let url = URL.init(fileURLWithPath: path!)
let player = AVPlayer(url: url)
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = videoView.bounds
self.videoView.layer.insertSublayer(playerLayer, at: 0)
player.play()
// Create observer to monitor when video ends, when it does so set the video time to the start (zero)
NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime,object: player.currentItem, queue: nil)
{
Notification in player.seek(to: kCMTimeZero)
player.play()
}
func shadowBackground(){
infoCardView.layer.cornerRadius = 3.0
infoCardView.layer.shadowColor =
UIColor.black.withAlphaComponent(0.6).cgColor
infoCardView.layer.shadowOffset = CGSize(width: 0, height: 0)
infoCardView.layer.shadowOpacity = 0.9
videoView.layer.cornerRadius = 3.0
videoView.layer.shadowColor =
UIColor.black.withAlphaComponent(0.6).cgColor
videoView.layer.shadowOffset = CGSize(width: 0, height: 0)
videoView.layer.shadowOpacity = 0.9
}
override func viewDidAppear(_ animated: Bool) {
setLoopingVideo()
}
#IBAction func showNextVideo(_ sender: UIButton) {
if currentVidIndex < videoId.count{
currentVidIndex += 1
print (currentVidIndex)
} else {
NSLog("Nothing")
}
}
What it looks like
If you want to change a video on every button click, you have to create always new AVPlayer object.
create and remember your AVPlayer object
create and remember your AVPlayerLayer
-- when button clicked --
remove AVPlayerLayer from the parent
stop AVPlayer (old video)
goto step 1 with new video URL
Here was my solution:
func clearVid(){
if let player = self.player{
player.pause()
self.player = nil
}
if let layer = self.playerLayer{
layer.removeFromSuperlayer()
self.playerLayer = nil
}
self.videoView.layer.sublayers?.removeAll()
}

iOS Swift AVAudioPlayer how to reset currentTime to zero when finished playing

My code is supposed to play a C Major scale wave file on a button press. Hitting a separate stop button will stop playback and reset the currentTime to zero.
#IBAction func onPlayButtonClick(sender: UIButton)
{
play.enabled = false
let path = NSBundle.mainBundle().pathForResource("cmajor", ofType: "wav")!
let url = NSURL(fileURLWithPath: path)
do
{
scalePlayer = try AVAudioPlayer(contentsOfURL: url)
scalePlayer.prepareToPlay()
scalePlayer.enableRate = true
scalePlayer.rate = 0.75
scalePlayer.play()
}
catch
{
}
}
It works as intended, but how do I set the currentTime to zero when the file is finished playing? I couldn't find anything on the developer docs.
You have to set a delegate:
scalePlayer.delegate = self
Then you implement this callback
func audioPlayerDidFinishPlaying(player: AVAudioPlayer, successfully flag: Bool) {
//set the current time here
scalePlayer.currentTime = 0
}
Update
Here is an example how you can implement this (based on your code):
class MyViewController: UIViewController, AVAudioPlayerDelegate {
#IBOutlet weak var play: UIButton!
private var scalePlayer: AVAudioPlayer?
override func viewDidLoad() {
super.viewDidLoad()
let path = NSBundle.mainBundle().pathForResource("cmajor", ofType: "wav")!
let url = NSURL(fileURLWithPath: path)
scalePlayer = try? AVAudioPlayer(contentsOfURL: url)
scalePlayer?.delegate = self
}
#IBAction func onPlayButtonClick(sender: UIButton) {
play.enabled = false
scalePlayer?.prepareToPlay()
scalePlayer?.enableRate = true
scalePlayer?.rate = 0.75
scalePlayer?.play()
}
func audioPlayerDidFinishPlaying(player: AVAudioPlayer, successfully flag: Bool) {
scalePlayer?.currentTime = 0
}
}

How do i make a looping video in swift

How would i go about making a local .mp4 file with no sound play on a loop, so it would only take up part of the screen and have no user controls. Just a looping video, sort of like a gif. I am using xcode, swift2.
class ViewController: UIViewController {
var playerViewController = AVPlayerViewController()
var playerView = AVPlayer()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidAppear(animated: Bool) {
var fileURL = NSURL(fileURLWithPath: "/Users/Mantas/Desktop/123/123/video-1453562323.mp4.mp4")
playerView = AVPlayer(URL: fileURL)
playerViewController.player = playerView
self.presentViewController(playerViewController, animated: true){
self.playerViewController.player?.play()
}
}
}
I have made this, it plays the video, but in full screen, i dont know how to make it only take up part of the screen and how to make it loop
Alternate version in Swift 3.0:
Add these properties:
fileprivate var player: AVPlayer? {
didSet { player?.play() }
}
fileprivate var playerObserver: Any?
Add this to your deinit:
deinit {
guard let observer = playerObserver else { return }
NotificationCenter.default.removeObserver(observer)
}
Add this function:
func videoPlayerLayer() -> AVPlayerLayer {
let fileURL = URL(fileURLWithPath: mediaPath)
let player = AVPlayer(url: fileURL)
let resetPlayer = {
player.seek(to: kCMTimeZero)
player.play()
}
playerObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem, queue: nil) { notification in
resetPlayer()
}
self.player = player
return AVPlayerLayer(player: player)
}
Then add to your layer wherever you feel like (viewDidLoad, viewDidAppear, viewDidFinishLayingOutSubviews):
let playerLayer = videoPlayerLayer()
playerLayer.frame = view.bounds
view.layer.insertSublayer(playerLayer, at: 0)
Adding observer when video going to finish you can make replay the video
override func viewDidAppear(animated: Bool) {
super.viewDidAppear()
var fileURL = NSURL(fileURLWithPath: "/Users/Mantas/Desktop/123/123/video-1453562323.mp4.mp4")
playerView = AVPlayer(URL: fileURL)
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "playerItemDidReachEnd:",
name: AVPlayerItemDidPlayToEndTimeNotification,
object: self.playerView.currentItem) // Add observer
playerViewController.player = playerView
//amend the frame of the view
self.playerViewController.player.frame = CGRectMake(0, 0, 200, 200)
//reset the layer's frame, and re-add it to the view
var playerLayer: AVPlayerLayer = AVPlayerLayer.playerLayerWithPlayer(self.playerView)
playerLayer.frame = videoHolderView.bounds
videoHolderView.layer.addSublayer(playerLayer)
/* Full Screen
self.presentViewController(playerViewController, animated: true){
self.playerViewController.player?.play()
} */
}
func playerItemDidReachEnd(notification: NSNotification) {
self.playerView.seekToTime(kCMTimeZero)
self.playerView.play()
}
For a seemless repeating video without a black flash. Use the AVPlayerLooper like so:
private var player: AVQueuePlayer!
private var playerLayer: AVPlayerLayer!
private var playerItem: AVPlayerItem!
private var playerLooper: AVPlayerLooper!
override func viewDidLoad(){
super.viewDidLoad()
let path = Bundle.main.path(forResource: "background_cloudy", ofType: "mov")
let pathURL = URL(fileURLWithPath: path!)
let duration = Int64( ( (Float64(CMTimeGetSeconds(AVAsset(url: pathURL).duration)) * 10.0) - 1) / 10.0 )
player = AVQueuePlayer()
playerLayer = AVPlayerLayer(player: player)
playerItem = AVPlayerItem(url: pathURL)
playerLooper = AVPlayerLooper(player: player, templateItem: playerItem,
timeRange: CMTimeRange(start: kCMTimeZero, end: CMTimeMake(duration, 1)) )
playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
playerLayer.frame = view.layer.bounds
view.layer.insertSublayer(playerLayer, at: 1)
}
This is tested with Swift 5, I found in https://gist.github.com/lanserxt/33fd8c479185cba181497315299e0e31
import UIKit
import AVFoundation
class LoopedVideoPlayerView: UIView {
fileprivate var videoURL: URL?
fileprivate var queuePlayer: AVQueuePlayer?
fileprivate var playerLayer: AVPlayerLayer?
fileprivate var playbackLooper: AVPlayerLooper?
func prepareVideo(_ videoURL: URL) {
let playerItem = AVPlayerItem(url: videoURL)
self.queuePlayer = AVQueuePlayer(playerItem: playerItem)
self.playerLayer = AVPlayerLayer(player: self.queuePlayer)
guard let playerLayer = self.playerLayer else {return}
guard let queuePlayer = self.queuePlayer else {return}
self.playbackLooper = AVPlayerLooper.init(player: queuePlayer, templateItem: playerItem)
playerLayer.videoGravity = .resizeAspectFill
playerLayer.frame = self.frame
self.layer.addSublayer(playerLayer)
}
func play() {
self.queuePlayer?.play()
}
func pause() {
self.queuePlayer?.pause()
}
func stop() {
self.queuePlayer?.pause()
self.queuePlayer?.seek(to: CMTime.init(seconds: 0, preferredTimescale: 1))
}
func unload() {
self.playerLayer?.removeFromSuperlayer()
self.playerLayer = nil
self.queuePlayer = nil
self.playbackLooper = nil
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
self.playerLayer?.frame = self.bounds
}
}

Remote mp3 file taking a lot of time to play in swift ios

i'm in trouble. i want to play a remote mp3 file in my app. but mp3 file taking a lot of time (approx 5-6 minute) to play. why ?
Anyone can suggest what should i do ?
import UIKit
import AVFoundation
class TestViewController: UIViewController, AVAudioPlayerDelegate {
var player:AVAudioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func play(sender: AnyObject) {
let url = "http://www.example.com/song.mp3"
let fileURL = NSURL(string: url.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()))
let soundData = NSData.dataWithContentsOfURL(fileURL, options: nil, error: nil)
var error: NSError?
self.player = AVAudioPlayer(data: soundData, error: &error)
if player == nil
{
if let e = error
{
println(e.localizedDescription)
}
}
player.volume = 1.0
player.delegate = self
player.prepareToPlay()
player.play()
}
}
Thanks in advance.
Use AVPlayer instead of AVAudioPlayer for streaming audio. It can be as simple as this --
let urlString = "http://www.example.com/song.mp3"
let url = NSURL(string: urlString)
var avPlayer = AVPlayer(URL: url)
avPlayer.play()
You can also set an observer on the AVPlayer.status property to manage its changing status. Check out:
https://stackoverflow.com/a/13156942/2484290 (objective c)
And of course the AVPlayer docs are here:
https://developer.apple.com/LIBRARY/ios/documentation/AVFoundation/Reference/AVPlayer_Class/index.html#//apple_ref/occ/cl/AVPlayer
Try using my example where you can work with the cache and the remote file.
player.automaticallyWaitsToMinimizeStalling = false // by default true, so It is important to solve your problem in a mp3 file remotely
This feature is available iOS 10.0+
var player: AVPlayer!
enum AudioType: String {
case remote
case cache
}
#IBAction func remotePressed(_ sender: Any) {
playAudio(type: .remote, fileURL: "http://www.example.com/song.mp3")
}
#IBAction func cachePressed(_ sender: Any) {
if let fileURL = Bundle.main.path(forResource: "jordan", ofType: "mp3") {
playAudio(type: .cache, fileURL: fileURL)
}
}
private func playAudio(type: AudioType, fileURL: String) {
let url = type == .cache ? URL.init(fileURLWithPath: fileURL) : URL.init(string: fileURL)
let playerItem: AVPlayerItem = AVPlayerItem(url: url!)
player = AVPlayer(playerItem: playerItem)
player.automaticallyWaitsToMinimizeStalling = false//It's important
player.play()
}
Its may be late.
Yes. what #lemon lime pomelo has said. you should use AVPlayer instead of AVAudioPlayer.
Also you can use MediaPlayer.
import UIKit
import MediaPlayer
#IBAction func playSong(sender: AnyObject) {
print("Song")
self.playAudio("yourAudioURL.mp3")
}
func playAudio(URL:String){
let movieurl:NSURL? = NSURL(string: "\(URL)")
if movieurl != nil {
self.movie = MPMoviePlayerViewController(contentURL: movieurl!)
}
if self.movie != nil {
self.presentViewController(self.movie!, animated: true, completion: nil)
self.movie?.moviePlayer.play()
}
}
It is slow because your program downloads all the song before starting playing

Resources