Keeping a UIButton selected while audio is playing - ios

I created the button with Image on MainStoryBoard. When I press that button it plays audio file.
I would like that button to stay highlighted during the time audio file is playing. When the audio is finished, I want the button to return to its default state.
I discovered that audioPlayerDidFinishPlaying(_:successfully:) is a right way, but can not solve this problem. Is it close to the proper way to do it? If not, how should I?
I'd appreciate any advice that will lead to finding the solution to this problem. Thanks.
let url = Bundle.main.bundleURL.appendingPathComponent("audio1.mp3")
override func viewDidLoad() {
super.viewDidLoad()
}
func play(url: URL) {
do {
try player = AVAudioPlayer(contentsOf:url)
player.play()
} catch {
print(error)
}
}
#IBAction func pushButton1(sender: UIButton) {
play(url: url)
}

On viewDidLoad, you can set default property when it is not playing (eg: I'm keeping background color as white) and while playing, make that button as selected(eg: I'm making it color Green)
Again in audioPlayerDidFinishPlaying when it stops playing audio, make it to default state (green to white again):
#IBOutlet weak var yourButton: UIButton!
let url = Bundle.main.bundleURL.appendingPathComponent("audio1.mp3")
override func viewDidLoad() {
super.viewDidLoad()
self.yourButton.backgroundColor = UIColor.white // default state of button
}
#IBAction func pushButton1(sender: UIButton) {
play(url: url)
}
func play(url: URL) {
do {
try player = AVAudioPlayer(contentsOf:url)
player.play()
self.yourButton.backgroundColor = UIColor.green // playing
}
catch {
print(error)
}
}
func audioPlayerDidFinishPlaying(player: AVAudioPlayer!, successfully flag: Bool) {
self.yourButton.backgroundColor = UIColor.white // back to default state
}

Related

Keep AVSpeechSynthesizer playing if another view is presented modally

I am trying to utilize the AVSpeechSynthesizer in swift/xcode to read out some text. I have it working for the most part. I have it set so that if they go back to the previous screen or to the next screen, the audio will stop. But in my instance, I want the speech to continue if another view is presented modally. As an example, I have an exit button that once clicked presents an "Are you sure you want to exit? y/n" type screen, but I want the audio to continue until they click yes and are taken away. I also have another view that can be presented modally, again, wanting the audio to continue if this is the case.
Does anyone have an idea of how I can keep the speech playing when a view is presented modally over top but stop playing when navigating to another view entirely?
Here is my code so far:
//Press Play/Pause Button
#IBAction func playPauseButtonAction(_ sender: Any) {
if(isPlaying){
//pause
synthesizer.pauseSpeaking(at: AVSpeechBoundary.immediate)
playPauseButton.setTitle("Play", for: .normal)
} else {
if(synthesizer.isPaused){
//resume playing
synthesizer.continueSpeaking()
} else {
//start playing
theUtterance = AVSpeechUtterance(string: audioTextLabel.text!)
theUtterance.voice = AVSpeechSynthesisVoice(language: "en-UK")
synthesizer.speak(theUtterance)
}
playPauseButton.setTitle("Pause", for: .normal)
}
isPlaying = !isPlaying
}
//Press Stop Button
#IBAction func stopButtonAction(_ sender: Any) {
if(isPlaying){
//stop
synthesizer.stopSpeaking(at: AVSpeechBoundary.immediate)
playPauseButton.setTitle("Play", for: .normal)
isPlaying = !isPlaying
}
}
//Leave Page
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
synthesizer.stopSpeaking(at: .immediate)
}
The problem lies in your viewWillDisappear. Any kind of new screen will trigger this, so your code indeed synthesizer.stopSpeaking(at: .immediate) will be invoked, thus stopping your audio. And that includes presentation or pushing a new controller.
Now, how to improve that? You've mentioned this:
I have it set so that if they go back to the previous screen or to the
next screen, the audio will stop
First off, if they go back to the previous screen:
You'd want to execute the same stopping of audio code line inside your
deinit { } method. That will let you know that your screen or controller is being erased from the memory, meaning the controller is gone in your controller stacks (the user went back to the previous screen). This should work 100% fine as long as you don't have retain cycle count issue.
Next, to the next screen, easily, you could include the same code line of stopping your audio inside your function for pushing a new screen.
After a lot of research I was able to get the desired functionality. As suggested by Glenn, the correct approach was to get the stopSpeaking to be called in deinit instead of viewWillDisappear. The issue was that using AVSpeechSynthesizer/AVSpeechSynthesizerDelegate normally would create a strong reference to the ViewController and, therefore, deinit would not be called. To solve this I had to create a custom class that inherits from AVSpeechSynthesizerDelegate but uses a weak delegate reference to a custom protocol.
The custom class and protocol:
import UIKit
import AVFoundation
protocol AudioTextReaderDelegate: AnyObject {
func speechDidFinish()
}
class AudioTextReader: NSObject, AVSpeechSynthesizerDelegate {
let synthesizer = AVSpeechSynthesizer()
weak var delegate: AudioTextReaderDelegate!
//^IMPORTANT to use weak so that strong reference isn't created.
override init(){
super.init()
self.synthesizer.delegate = self
}
func startSpeaking(_ toRead: String){
let utterance = AVSpeechUtterance(string: toRead)
synthesizer.speak(utterance)
}
func resumeSpeaking(){
synthesizer.continueSpeaking()
}
func pauseSpeaking(){
synthesizer.pauseSpeaking(at: .immediate)
}
func stopSpeaking() {
synthesizer.stopSpeaking(at: .immediate)
}
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
self.delegate.speechDidFinish()
}
}
And then inheriting and using it in my ViewController:
import UIKit
import AVFoundation
class MyClassViewController: UIViewController, AudioTextReaderDelegate{
var isPlaying = false
let audioReader = AudioTextReader()
let toReadText = "This is the text to speak"
#IBOutlet weak var playPauseButton: UIButton!
#IBOutlet weak var stopButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
self.audioReader.delegate = self
}
//Press Play/Pause Button
#IBAction func playPauseButtonAction(_ sender: Any) {
if(isPlaying){
//Pause
audioReader.synthesizer.pauseSpeaking(at: .immediate)
playPauseButton.setTitle("Play", for: .normal)
} else {
if(audioReader.synthesizer.isPaused){
//Resume Playing
audioReader.resumeSpeaking()
} else {
audioReader.startSpeaking(toReadText)
}
playPauseButton.setTitle("Pause", for: .normal)
}
isPlaying = !isPlaying
}
//Press Stop Button
#IBAction func stopButtonAction(_ sender: Any) {
if(isPlaying){
//Change Button Text
playPauseButton.setTitle("Play", for: .normal)
isPlaying = !isPlaying
}
//Stop
audioReader.stopSpeaking()
}
//Finished Reading
func speechDidFinish() {
playPauseButton.setTitle("Play", for: .normal)
isPlaying = !isPlaying
}
//Leave Page
deinit {
audioReader.stopSpeaking()
playPauseButton.setTitle("Play", for: .normal)
isPlaying = !isPlaying
}
#IBAction func NextStopButton(_ sender: Any) {
audioReader.stopSpeaking()
playPauseButton.setTitle("Play", for: .normal)
isPlaying = !isPlaying
}
}
I hope this helps someone in the future, because this was driving me up the wall.

viewWillDisappear error/stop playing sound when i change viewController

I have some ViewCotroller where i tapped a button - some audio file is playing. I want to stop playing audio when change/close this ViewController. How I can do this?
I try add some method to viewWillDisappear and it crashed if I don't tap any button. But if I tapped some button - it will works fine. 🙁
import UIKit
import AVFoundation
class BossViewController: UIViewController {
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
//MARK: - Stop sound when we're change/close this viewController
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
audioPlayer.stop()
}
This is how my button is look like:
#IBAction func boss1(_ sender: AnyObject) {
let audioFile = NSURL(fileURLWithPath: Bundle.main.path(forResource: "shamp", ofType: "mp3")!)
do {
audioPlayer = try AVAudioPlayer(contentsOf: audioFile as URL)
audioPlayer.prepareToPlay()
} catch {
print("Problem in getting File")
}
audioPlayer.play()
}
You can check if the audio player is playing, as #SandeepBhandari mentioned, or you can use a Bool variable to keep track whether or not the user tapped the button:
var buttonTapped = false
#IBAction func boss1(_ sender: AnyObject) {
...
buttonTapped = true
}
Then, you can check the variable in your viewWillDisappear(_ animated: Bool) method:
override func viewWillDisappear(_ animated: Bool) {
if buttonTapped {
audioPlayer.stop()
}
}

Ios Swift 3 automatically playing a video after the page loads

Im currently using 'AVKit' and 'AVFoundation' to enable the video playing through my app but I can only done by starting the video by tapping a button. But I am currently facing a problem of trying to making an automatic function like playing the video footage right after we start an app on an iOS device.
override func viewDidLoad()
{
super.viewDidLoad()
self.playingVideo()
self.nowPlaying(self)
}
#IBAction func nowPlaying(_ sender: Any)
{
self.present(self.playerController, animated: true, completion:{
self.playerController.player?.play()
})
after I compiled it, the system printed out:
Warning: Attempt to present on whose view is not in the window hierarchy!
Please try following working code.
import AVFoundation
import UIKit
class SomeViewController: UIViewController {
func openVideo() {
let playerController = SomeMediaPlayer()
playerController.audioURL = URL(string: "Some audio/video url string")
self.present(playerController, animated: true, completion: nil)
}
}
class SomeMediaPlayer: UIViewController {
public var audioURL:URL!
private var player = AVPlayer()
private var playerLayer: AVPlayerLayer!
override func viewDidLoad() {
super.viewDidLoad()
self.playerLayer = AVPlayerLayer(player: self.player)
self.view.layer.insertSublayer(self.playerLayer, at: 0)
let playerItem = AVPlayerItem(url: self.audioURL)
self.player.replaceCurrentItem(with: playerItem)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.player.play()
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
self.playerLayer.frame = self.view.bounds
}
// Force the view into landscape mode if you need to
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
get {
return .landscape
}
}
}

objects of view controller hide behind of video background in swift

i want create a login page for my app, in this case i want to use a video for background of login page, like vine,spotify,uber,...
i play video without problem and add some button and lable in storyboard to my view controller، after run app i can not see objects of view controller
i just see video and objects hide behind of video background how can fix this problem,,,
thanks for helping
this is my code for playing video,,,
lazy var playerLayer:AVPlayerLayer = {
let player = AVPlayer(URL: NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("BWWalkthrough", ofType: "mp4")!))
player.muted = true
player.allowsExternalPlayback = false
player.appliesMediaSelectionCriteriaAutomatically = false
var error:NSError?
// This is needed so it would not cut off users audio (if listening to music etc.
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient)
} catch var error1 as NSError {
error = error1
} catch {
fatalError()
}
if error != nil {
print(error)
}
var playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = self.view.frame
playerLayer.videoGravity = "AVLayerVideoGravityResizeAspectFill"
playerLayer.backgroundColor = UIColor.blackColor().CGColor
player.play()
NSNotificationCenter.defaultCenter().addObserver(self, selector:"playerDidReachEnd", name:AVPlayerItemDidPlayToEndTimeNotification, object:nil)
return playerLayer
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.view.layer.addSublayer(self.playerLayer)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
override func willAnimateRotationToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
playerLayer.frame = self.view.frame
}
func playerDidReachEnd(){
self.playerLayer.player!.seekToTime(kCMTimeZero)
self.playerLayer.player!.play()
}
You need to add your playerLayer below all the views created when your storyboard loads. Replacing
self.view.layer.addSublayer(self.playerLayer)
with
self.view.layer.insertSublayer(self.playerLayer, atIndex:0)
should do the job, unless there are any subviews of the view controller view that you actually want to be behind the video layer.

SWIFT: MediaPlayer - Done Button doesn't do anything

so I have the following code working great! for anyone wanting to get a video working in SWIFT. The issue is, after the video finishes it remains on the video. Even when pressing Done nothing happens...
How can I close the video back to one of the existing View Controllers?
class ViewController: UIViewController {
var moviePlayer : MPMoviePlayerController?
override func viewDidLoad() {
super.viewDidLoad()
playVideo()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func playVideo() {
//Get the Video Path
//You need to put this in Project->Target->copy bundle resource for this to work
let videoPath = NSBundle.mainBundle().pathForResource("Quizzes", ofType:"mov")
//Make a URL from your path
let url = NSURL.fileURLWithPath(videoPath!)
//Initalize the movie player
moviePlayer = MPMoviePlayerController(contentURL: url)
if let player = moviePlayer {
//Make the player scale the entire view
player.view.frame = self.view.bounds
player.scalingMode = .AspectFill
//Add it as a subView to your currentView
self.view.addSubview(player.view)
//Play the video
player.prepareToPlay()
}
else {
print("Movie player couldn't be initialized")
}
}
You have added it as subview
self.view.addSubview(player.view)
You also need to remove it where is that code?
Please use removeFromSuperview when done playing.

Resources