I have two View Controllers a TableViewController where I have a list of musics and a UIViewController where it plays the music. The music automatically plays when the view is loaded and pauses when the pause button is pressed.
However whenever I go back to the previous TableViewController to select another music, the music continues to play. And if i select another music, both of them are playing together.
My Codes:
var audioplayer = AVPlayer()
override func viewDidLoad() {
super.viewDidLoad()
play()
}
func play(){
var songUrl = songList[selectedItem].url
audioplayer = AVPlayer(url: songUrl!)
audioplayer.play()
}
Have you tried to use .pause() ? example:
var audioplayer: AVPlayer? = AVPlayer()
override func viewDidLoad() {
super.viewDidLoad()
play()
}
func play(){
var songUrl = songList[selectedItem].url
audioplayer = AVPlayer(url: songUrl!)
audioplayer.play()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
audioplayer.pause()
audioplayer = nil
}
I already fixed my problem. I remove all player items in view controller and create new class for player. Now ıt ıs workıng smoothly.
Related
I am making a very simple IOS app. It is a button, and when pressed a long .wav file plays. I would love to be able to press the button again, and the audio would stop playing. I’ve tried many of the solutions here as there are similar questions, but I’m stuck. Any ideas?
import UIKit
import AVFoundation
class ViewController: UIViewController {
// Making a variable called player. It is the AVAudioPlayer
var player: AVAudioPlayer!
// No idea what this is.
override func viewDidLoad() {
super.viewDidLoad()
}
//This is the key pressed. Inside is the playSound and a print for checking
#IBAction func keyPressed(_ sender: UIButton) {
playSound()
print("button pressed")
}
//Turn playSound into a func – and tell it what to play and play it if button is pressed
func playSound() {
let url = Bundle.main.url(forResource: "audio", withExtension: "wav")
player = try! AVAudioPlayer(contentsOf: url!)
player.play()
}
}
AVAudioPlayer has the isPlaying (docs) property that you can check to determine if you should play or stop playback when your button is pressed.
import AVFoundation
import UIKit
class ViewController: UIViewController {
// Making a variable called player. It is the AVAudioPlayer
var player: AVAudioPlayer!
// This is the key pressed. Inside is the playSound and a print for checking
#IBAction func keyPressed(_ sender: UIButton) {
if player.isPlaying {
stopSound()
} else {
playSound()
}
}
// Turn playSound into a func – and tell it what to play and play it if button is pressed
func playSound() {
let url = Bundle.main.url(forResource: "audio", withExtension: "wav")
player = try! AVAudioPlayer(contentsOf: url!)
player.play()
}
func stopSound() {
player.stop()
}
}
Sidenote: The viewDidLoad method override isn't needed unless you're wanting to have custom code run when the view is loaded by the system.
I have set it up so that when a ViewController is displayed, a video starts automatically and at the end it switches to a different ViewController.
The problem is that if the app is put in the background while viewing it, the video freezes and you have to restart the application.
I thought about setting the classic pause / play controllers to appear when you press the screen so you can continue watching, but I don't know how to do that.
Or do you have another solution to prevent the video from freezing?
import UIKit
import AVKit
import AVFoundation
class View8BaController: UIViewController {
func setupAVPlayer() {
let videoURL = Bundle.main.url(forResource: "8B-A", withExtension: "mp4") // Get video url
let avAssets = AVAsset(url: videoURL!) // Create assets to get duration of video.
let avPlayer = AVPlayer(url: videoURL!) // Create avPlayer instance
let avPlayerLayer = AVPlayerLayer(player: avPlayer) // Create avPlayerLayer instance
avPlayerLayer.frame = self.view.bounds // Set bounds of avPlayerLayer
self.view.layer.addSublayer(avPlayerLayer) // Add avPlayerLayer to view's layer.
avPlayer.play() // Play video
// Add observer for every second to check video completed or not,
// If video play is completed then redirect to desire view controller.
avPlayer.addPeriodicTimeObserver(forInterval: CMTime(seconds: 1, preferredTimescale: 1) , queue: .main) { [weak self] time in
if time == avAssets.duration {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SCENA7") as! SCENA7ViewController
self?.navigationController?.pushViewController(vc, animated: false)
}
}
}
//------------------------------------------------------------------------------
override func viewDidLoad() {
super.viewDidLoad()
}
//------------------------------------------------------------------------------
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.setupAVPlayer() // Call method to setup AVPlayer & AVPlayerLayer to play video
}
}
have you tried telling the video to play in the sceneDelegate?
add to the class, right above func setupAVPlayer(),
var avPlayer: AvPlayer!
then in your sceneDelegate outside any functions
let view8Ba = View8BaController()
to create an instance of the view controller. Then you can access the properties in the following:
func sceneWillEnterForeground(_ scene: UIScene) {
if view8Ba.viewIfLoaded?.window != nil {
view8Ba.avPlayer.play()
}
}
This will tell your video to start playing again when the app comes back from the background.
If you want to add a play/pause when you tap the screen you can add a tap gesture recognizer and another view to the current view controller and set the background to clear (in storyboard drag the new view to the white bar on top of the view controller)
then call
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
pauseScreenView.frame = View8BaController.bounds
}
This tells the new view you added where to be on the viewController.
In the IBAction of the tap gesture recognizer
#IBAction func screenTapped(_ sender: Any) {
View8BaController.addSubview(pauseScreenView)
DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) {
self.pauseScreenView.removeFromSuperview()
}
}
This adds the new view to the top of the viewController and then removes it after 10 seconds.
In that new view you can add a button that will play/pause your video
#IBAction func pauseVideo(_ sender: UIButton) {
if avPlayer.timeControlStatus == .playing {
avPlayer.pause()
pauseButton.setImage(playImage, for: .normal)
}else {
avPlayer.play()
pauseButton.setImage(pauseImage, for: .normal)
}
}
I'm creating an extremely simple game, and I've hidden the video controls.
#IBAction func playVideo1(_ sender: Any) {
// play video connected to button 1
guard let firstVideo = Bundle.main.path(forResource: "Video1", ofType:"mp4") else {
debugPrint("Video not found")
return
}
// create an AVPlayer, passing it mp4
let player = AVPlayer(url: URL(fileURLWithPath: firstVideo))
// Create a new AVPlayerViewController and pass it a reference to the player.
let controller = AVPlayerViewController()
controller.player = player
controller.showsPlaybackControls = false
// Modally present the player and call the player's play() method when complete.
present(controller, animated: true) {
player.play()
}
} // end playVideo1
One of the two options would be OK.
Option 1: Tap to close the video.
Option 2: Have the AVPlayer close automatically at the end of the video.
I appreciate any help.
Thanks!
You can add a tap gesture recognizer to AVPlayerViewController's view (for closing on tap), or you could subscribe to AVPlayerItemDidPlayToEndTime notification (for closing when video ends playing). Something like this:
#IBAction func playVideo1(_ sender: Any) {
// play video connected to button 1
guard let firstVideo = Bundle.main.path(forResource: "Video1", ofType:"mp4") else {
debugPrint("Video not found")
return
}
// create an AVPlayer, passing it mp4
let player = AVPlayer(url: URL(fileURLWithPath: firstVideo))
// Create a new AVPlayerViewController and pass it a reference to the player.
let controller = AVPlayerViewController()
controller.player = player
controller.showsPlaybackControls = false
//for closing when video ends
NotificationCenter.default.addObserver(self, selector: #selector(closePlayer), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: controller.player?.currentItem)
//for closing on tap
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(closePlayer))
controller.view.addGestureRecognizer(tapGestureRecognizer)
// Modally present the player and call the player's play() method when complete.
present(controller, animated: true) {
player.play()
}
} // end playVideo1
#objc func closePlayer() {
dismiss(animated: true)
//if you go notification route, don't forget to remove observer
NotificationCenter.default.removeObserver(self)
}
For closing the videoPlayer when on user tap you can use the following-
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(closePlayerOnTouch))
controller.view.addGestureRecognizer(tapGestureRecognizer)
and then add this after the button -
#objc func closePlayerOnTouch() {
dismiss(animated: true)
NotificationCenter.default.removeObserver(self)
}
#Predrag's answer is really awesome though.
import UIKit
import AVFoundation
class ViewController: UIViewController {
#IBOutlet weak var btnPlay: UIButton!
var player:AVPlayer?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func btnPress(sender: AnyObject) {
if (btnPlay.titleLabel?.text == "Play") {
initPlayer()
btnPlay.setTitle("Stop", forState: UIControlState.Normal)
} else {
stopPlayer()
btnPlay.setTitle("Play", forState: UIControlState.Normal)
}
}
func initPlayer() {
if let play = player {
print("playing")
play.play()
} else {
print("player allocated")
player = AVPlayer(URL: NSURL(string: "http://streaming.radio.rtl.fr/rtl-1-48-192")!)
print("playing")
player!.play()
}
}
func stopPlayer() {
if let play = player {
print("stopped")
play.pause()
player = nil
print("player deallocated")
} else {
print("player was already deallocated")
}
}
}
import UIKit
import AVFoundation
class BackgroundViewController: UIViewController {
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
playBackgroundStartMusic()
}
override func viewWillAppear(_ animated: Bool) {
audioPlayer.stop()
}
//MARK: - Background Music
func playBackgroundStartMusic() {
do {
audioPlayer = try AVAudioPlayer(contentsOf: URL.init(fileURLWithPath: Bundle.main.path(forResource: "IntroMusic", ofType: "wav")!))
audioPlayer.prepareToPlay()
audioPlayer.play()
audioPlayer.numberOfLoops = -1
}
catch {
print(error)
}
}
}
if you ever have a problem of music overlaying ate views changes, simply put the audioPlayer.stop(), from the AVFoundation at your button that present your next screen, that way background music will stop and not play again.
I'm making a musicPlayer app in Swift, which is based on Tab Bar Controller and now is creating the NowPlayingViewController which present the playing scene as below screenshot showed. This NowPlayingViewController will be opened when I click the table row from another tableViewController, and the song was played.
My question is that when I dismiss this View Controller to go back the previous view or move to other view of Tab Bar Controller, the music playing is stoped. After some googles, someone suggests to use singleton to make a shared session for music playing.
/// here is my project's storyboard
/// I change my code to add one Audio Manager.swift as the singleton class. However I'm not sure if I'm doing the right thing, need help. Also don't find method about getting playingSong variable from NowPlayingVewController...
import AVKit
class AudioManager {
// use Singleton pattern keep the music continuing when user move through the App.
static let sharedInstance = AudioManager()
var audioPlayer: AVAudioPlayer!
var playingSong: SongData?
private init() {
// config for audio background playing
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.mixWithOthers])
print("Playback OK")
try AVAudioSession.sharedInstance().setActive(true)
print("Session is Active")
} catch {
print(error)
}
}
func startMusic() {
do {
// how to retrieve the playingSong variable from NowPlayingViewController?
// playingSong =
try audioPlayer = AVAudioPlayer(contentsOf: playingSong!.url)
audioPlayer.prepareToPlay()
audioPlayer.play()
} catch {
print("could not load file")
}
}
func pauseMusic() {
audioPlayer.pause()
}
}
/// NowPlayViewController.swift, in case it is not fully aligned with above picture because it is just an example image to show the playing scene.
import UIKit
import AVKit
class NowPlayingViewController: UIViewController {
var playingSong: SongData?
var audioPlayer: AVAudioPlayer!
var isPlaying = true
var timer:Timer!
#IBOutlet weak var songTitle: UILabel!
#IBOutlet weak var songAlbum: UILabel!
#IBOutlet weak var songArtwork: UIImageView!
#IBOutlet weak var playOrPauseButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
songTitle.text = playingSong?.songName
songAlbum.text = playingSong?.albumName
songArtwork.image = playingSong?.albumArtwork
// start play music in AudioManager sharedInstance
AudioManager.sharedInstance.startMusic()
}
#IBAction func NaviBack(_ sender: UIButton) {
print("press button")
// not sure about this, use present modally of segue,
// so need to use dismiss to return to the previous scene/view.
self.dismiss(animated: true, completion: nil)
}
#IBAction func PlayOrPauseMusic(_ sender: UIButton) {
if isPlaying {
print("isPlaying")
AudioManager.sharedInstance.pauseMusic()
isPlaying = false
playOrPauseButton.setTitle("Pause", for: .normal)
} else {
print("isPaused")
AudioManager.sharedInstance.startMusic()
isPlaying = true
playOrPauseButton.setTitle("Play", for: .normal)
}
}
}
/// Sending data to NowPlayingViewController from tableViewController using Segue present modally.
// define segue to the transition to NowPlaying View
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "playingSong" {
if let indexPath = self.tableView.indexPathForSelectedRow {
let controller = segue.destination as! NowPlayingViewController
if resultSearchController.isActive {
controller.playingSong = filteredTableData[indexPath.row]
} else {
controller.playingSong = tableData[indexPath.row]
}
}
}
}
Oops, I found my AudioManger is actually worked by getting the playingSong: SongData from tableViewController when user click the cell row in this view. I declare this playingSong as static var in order to access/use it from AudioManager.
Now music is playing when user navigation through the Tab Bar Controller, update my code as below. Next step could be add a button on the left/right top of navi view to re-present the playing scene:)
Err, I found that it still has issue, the play and resume for the current song is working, but if I select another song in the tableViewController, then it still plays the previous song in NowPlayingView. The reason could be the init() only once, so I need to find a way to re-assign value to sharedInstance when select another cell row.
import AVKit
class AudioManager {
// use Singleton pattern keep the music continuing when user move through the App.
static let sharedInstance = AudioManager()
var audioPlayer: AVAudioPlayer!
var playingSong: SongData?
private init() {
// config for audio background playing
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.mixWithOthers])
print("Playback OK")
try AVAudioSession.sharedInstance().setActive(true)
print("Session is Active")
} catch {
print(error)
}
do {
playingSong = SongsTableViewController.playingSong
try audioPlayer = AVAudioPlayer(contentsOf: playingSong!.url)
audioPlayer.prepareToPlay()
} catch {
print("could not load file")
}
}
func playMusic() {
audioPlayer.play()
}
func pauseMusic() {
audioPlayer.pause()
}
}