AudioPlayer and lockscreen/control center control Swift [closed] - swift

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
I'm new on Swift. I write because I want to ask a question. Me and my friend we are developing an audio player, but we have a problem. The player also works in background and remote controls from the lockscreen and the control center work, but if music is interrupted by one of these two controls, the play/pause button of our player is not updated with the correct icon.
My question is, how can I make it clear to the player that the music was starting/stopping by one of the remote controls and the player to act accordingly, changing the icon of play/pause button? thanks a lot, I hope I was clear.

You need to use the MPRemoteCommandCenter to do this. For example in your viewControllers viewDidLoad() you can add this:
override func viewDidLoad() {
super.viewDidLoad()
UIApplication.shared.beginReceivingRemoteControlEvents()
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.pauseCommand.addTarget { (event) -> MPRemoteCommandHandlerStatus in
//Update your button here for the pause command
return .success
}
commandCenter.playCommand.addTarget { (event) -> MPRemoteCommandHandlerStatus in
//Update your button here for the play command
return .success
}
}
Just change the comments I have included to update your buttons UI. You will also need to import MediaPlayer and MediaPlayer.framework if you have not done so already.

Related

How do i create a time delay before the button appears unity [duplicate]

This question already has answers here:
How to make the script wait/sleep in a simple way in unity
(7 answers)
Closed 1 year ago.
I am creating an interactive video using Unity/C#. I am doing this using scenes and buttons that when clicked, go to the next scene or back. However, I want the buttons to appear once the video is finished. Is there any way I can add a delay to the buttons before they appear in the scene when playing?
For time delay you could use coroutines. Check it out here There is options like waitforseconds so you can delay your button apperance. here is sample code
private IEnumerator ShowBtn()
{
yield return new WaitWhile(() => videoplayer.isPlaying);
// show visibility of your button
}
And when you play video call this function like this
StartCoroutine(ShowBtn());
This should do the trick. isPlaying.
A simple script would look like this.
if(videoplayer.isPlaying == false && videoWasPlayed == true){
btn.active = true;
videoWasPlayed = false;
}
videoWasPlayed is used to check if the video was ever played. This would need to be set to true when the video is played.

Xcode 11 Swift 5 Switch button

I am creating a simple application, and if the user gets the answer correct it plays a sound and if the answer is wrong it plays another sound. I have created a SettingsViewController with a switch button. How do I make it so that when the switch is on, it keeps sound and when it’s off it disables all sound
Follow this tutorial to setup your switch button and read its value. Then when you read its value, mute/unmute the sound.
Save the value to UserDefaults
UserDefaults.standard.set(boolValue, forKey: "gameSound")
Retrieve the sound anywhere in the Application using
let sound = UserDefaults.standard.bool(forKey: "gameSound")

How to detect the end of a song in MusicKit?

I am attempting to use Apple's Music Kit within my application to listen to music. My issue is that the first song that plays will continually repeat. The first song plays correctly by using the following code:
let mediaArray = [songsApple?[songNumber!]]
let playableColledction = MPMediaItemCollection.init(items: mediaArray as! [MPMediaItem])
applicationMusicPlayer.setQueue(with: playableCollection)
applicationMusicPlayer.play()
self.activateAudioSession
The activateAudioSession method is:
do {
try audioSession.setCategory(AVAudioSessionCategoryPlayback)
try audioSession.setActive(true)
}
catch let error {
print(error.localizedDescription)
}
To detect the end of the song, I use the following observer from the Apple Music Documentation:
applicationMusicPlayer.beginGeneratingPlaybackNotications()
NotificationCenter.default.addObserver(self, #selector(didFinishSong), name: Notification.Name.MPMusicPlayerControllerNowPlayingItemDidChange, object: nil)
The didFinishSong method is used to get the songNumber of the next song to be played from the songsApple array. This information is then passed back into the method that plays the song to get the mediaArray. However, right now, didFinishSong does not get called when the song playing finishes and instead the song just repeats from the beginning.
Is there something that I am doing wrong for detecting the end of a song? Is there another way to do it? I also tried to use a timer, but for some reason it would stop counting whenever the application entered the background making the count inaccurate.
Thank you in advance for any help that you are able to provide. If you need additional information please to not hesitate to ask and I will clarify more.

WKWatchConnectivityRefreshBackgroundTask is never triggered in background, but WKSnapshotRefreshBackgroundTask

I want to update my watch app state in background from iPhone, using session.updateApplicationContext(applicationContext).
Sending an application contact while the app on the watch is active does work properly.
When I activate the home button on the watch, the watch app goes to the background, handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) is called, and a WKSnapshotRefreshBackgroundTask is provided.
So I don’t understand why a WKSnapshotRefreshBackgroundTask is triggered properly, but not a WKWatchConnectivityRefreshBackgroundTask.
Apple’s docs say „When you receive background data from the paired iPhone, the system launches your app in the background, instantiates a WKWatchConnectivityRefreshBackgroundTask object, and passes the task object to your extension delegate’s handleBackgroundTasks: method.“.
But this does not happen, neither on a device, nor on the simulator. What could be wrong?
Edit:
To check what might be wrong, I downloaded Apple’s demo project „QuickSwitch“ that can be downloaded here. Here is the code that should handle background tasks:
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
for backgroundTask in backgroundTasks {
if let wcBackgroundTask = backgroundTask as? WKWatchConnectivityRefreshBackgroundTask {
// store a reference to the task objects as we might have to wait to complete them
self.wcBackgroundTasks.append(wcBackgroundTask)
} else {
// immediately complete all other task types as we have not added support for them
backgroundTask.setTaskCompleted()
}
}
completeAllTasksIfReady()
}
There, the same happens:
I did set a breakponint in the line of the if statement and executed the app.
When the home button on the watch simulator is pressed, the breakpoint is reached with a WKSnapshotRefreshBackgroundTask. This is OK (see above).
However, if a different line is selected on the iPhone simulator, watchOS does not schedule a WKWatchConnectivityRefreshBackgroundTask, as expected. After all, this demo project should demo exactly this point.
Maybe somebody could try the demo project and confirm this problem or not.
What is wrong?
Update my answer
Conclusion first
Currently WKWatchConnectivityRefreshBackgroundTask is only called for sure on a watchOS Simulator when the watchOS extension's WCSession is in notActivated state and the extension is not running in foreground (in background or terminated).
In real devices, it won't be called in my tests. But Apple docs says it may. So you shouldn't rely on it won't be called until Apple changes its docs.
WCSession Cores
For WCSession, when it is activated, you can transfer userInfo, and when the counterpart is active, it can get the userInfo. The counterpart won't need to be in foreground to be activated, it can be in a high priority background.
Testing Results
Here are my testing results.
How to make WCSession notActivated?
Using Xcode terminate your watchOS Extension. Xcode will send a kill signal to your WKExtension.
Or don't run WCSession.activate() in your code on watchOS Extension side. As WCSession is notActivated by default.
-------------below are old post, you can ignore safely if you don't want to read.-------------------
Theory
Please watch the picture first then I will explain.
Because of the history of watchOS, there are both WCSessionDelegate receiving functions (start from watchOS 2.0) and WKExtensionDelegate.handle(_:) function (start from watchOS 3.0).
Although they all claims to be background dealing, the former only works immediately when your app is in foreground. The data will be queued if your app is not in foreground (in background or being terminated) and executed immediately later when your app becomes in foreground again.
WKExtensionDelegate.handle(_:) is really working in background. However, the WKExtensionDelegate.handle(_:) is optional although it is recommended and well-prepared if you use Xcode.
If you don't implement WKExtensionDelegate.handle(_:) by commenting it. You app works in a watchOS 2.0 way.
If you implement WKExtensionDelegate.handle(_:) but you don't have a WCSession in your watchOS app. The result is tricky. You won't get any data when you watchOS app is in foreground, as you don't has a WCSession. When your app is in background, it will be waken when data comes, but you can't get the data as you don't have a session.
If you implemented them both, which are in most situations, data comes will be dealt depending on the state of your watchOS app and never be queued.
How to proving it?
Create a new watchOS project. In iOS part, add a button, each time you clicked the button, send a userInfo to watchOS
session.transferUserInfo(["send test":""])
In your watchOS app, add a label in interface.storyboard, and drag it to viewController as #IBOutlet var label: WKInterfaceLabel!, and implement both WKExtensionDelegate.handle(_:) and func session(WCSession, didReceiveUserInfo: [String : Any] = [:]) appDelegate.
var total = 0
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
// Sent when the system needs to launch the application in the background to process tasks. Tasks arrive in a set, so loop through and process each one.
for task in backgroundTasks {
// Use a switch statement to check the task type
switch task {
case let backgroundTask as WKApplicationRefreshBackgroundTask:
// Be sure to complete the background task once you’re done.
backgroundTask.setTaskCompleted()
case let snapshotTask as WKSnapshotRefreshBackgroundTask:
// Snapshot tasks have a unique completion call, make sure to set your expiration date
snapshotTask.setTaskCompleted(restoredDefaultState: true, estimatedSnapshotExpiration: Date.distantFuture, userInfo: nil)
case let connectivityTask as WKWatchConnectivityRefreshBackgroundTask:
// Be sure to complete the connectivity task once you’re done.
total += 1
DispatchQueue.main.async {
if let viewController = WKExtension.shared().rootInterfaceController as? InterfaceController {
viewController.label.setText(String(self.total))
}
}
connectivityTask.setTaskCompleted()
case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
// Be sure to complete the URL session task once you’re done.
urlSessionTask.setTaskCompleted()
default:
// make sure to complete unhandled task types
task.setTaskCompleted()
}
}
}
public func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
total += 4
DispatchQueue.main.async {
if let viewController = WKExtension.shared().rootInterfaceController as? InterfaceController {
viewController.label.setText(String(self.total))
}
}
}
If WKExtensionDelegate.handle(_:) runs, we add total by 1. If func session(WCSession, didReceiveUserInfo: [String : Any] = [:]) runs, we add total by 4.
Debug
In Xcode, choose product->scheme as WatchKit app so we can terminate the watchOS app in Xcode.
run the project.
when watchOS app shows, open iOS app manually.
clicked the button in iOS app. You can see the label in watchOS changes by 4.
in Xcode, click product->stop(or cmd+.). watchOS app will disappear.
click one or more times on iOS app's button. Then manually open the watchOS app. You will see this time the label changes by 1 multiply your clicks.
The step will be 4 again when watchOS app is in foreground.
To all who have the same problem:
I submitted the problem to Apple Developer Technical Support, and they confirmed (# 652471299) the problem in watchOS 3, and suggested to file a bug report, what I did (# 29284559).
So, one has to wait for a bug fix by Apple.
Update:
They answered my bug report, only 2 days later:
Well we get a ton of issues like this, usually it is some misunderstanding about timings or the app not suspending because it is being debugged or not in the dock so it won’t get discretionary tasks.  
In this case, reading the description above I’m guessing the user is debugging via xcode while testing.  Two task types: Watch Connectivity and URLSession only arrive as “launch” events.  When debugging, xcode keeps the app running so it will never get these tasks.  The best way to test this is to disconnect from xcode and test, make sure your app is in the dock as well — only docked apps will get discretionary tasks.
If you see this not working after trying that we’ll need a sysdiagnose to go further.
I think this statement is wrong. My reply was:
Thanks for the quick answer. However, something is wrong anyway:
The function
handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>)
should handle all background tasks, including WKWatchConnectivityRefreshBackgroundTask.
To check that this is not the case is easy:
Just let the app crash when such a background task is scheduled, i.e. insert in Apple’s QuickSwitch demo project an assert statement that is always false:
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
for backgroundTask in backgroundTasks {
if let wcBackgroundTask = backgroundTask as? WKWatchConnectivityRefreshBackgroundTask {
assert(false) // If the app comes here, it will crash
// store a reference to the task objects as we might have to wait to complete them
self.wcBackgroundTasks.append(wcBackgroundTask)
} else {
// immediately complete all other task types as we have not added support for them
backgroundTask.setTaskCompleted()
}
}
completeAllTasksIfReady()
}
Then run the app in foreground, in the dock, or in background, and select different codes on the iPhone.
The app will NOT crash, which proves that no WKWatchConnectivityRefreshBackgroundTask is scheduled.
Please do this test without Xcode control. Just run it on iPhone & watch devices.
Now, 1 week later, I did not get any more reply.
Maybe I am wrong, and somebody can give me a hint how to do it right.

RevMobAds - Test video ads not loading?

Ive opted towards RevMob since the iAd network isn't accepting new apps and I'm trying to put a video ad after a round of my game is played like this:
func gameOver() {
RevMobAds.session().fullscreen().loadVideo()
RevMobAds.session().fullscreen().showVideo()
let transition = SKTransition.fadeWithDuration(0.5)
let gameScene = GameOver(size: self.size)
self.view!.presentScene(gameScene, transition: transition)
}
My fullscreen and banner ads are working perfectly, but when I end the game to load the video, I get this in the console with no ad being shown:
[RevMob] Ad received: (200) - 56ba71998e700003764c65b9
Is anyone else having this problem? If so, have you fixed it?
We have changed our documentation, please checkout the updated one here.
What I believe is happening is that you are not saving the fullscreen object. You should create a fullscreen object and save it into a variable:
video = RevMobAds.session().fullscreen()
Then you can load a video ad into the fullscreen object: video!.loadVideo()
And then showVideo on the fullscreen object: video!.showVideo()
Note that loadVideo() is an asynchronous call, so you should either call the methods with completion handlers or extend our delegate classes.
Best regards,