MPMusicPlayerController being extremely slow (lagging 1+ second) - swift

I am trying to make a music player and my app is being really slow when i added the player in there...
I can't switch more than a couple songs within 5 seconds or my app will freeze
let myCollection = MPMediaItemCollection(items: mySongs)
myController.setQueue(with: myCollection)
//setting the album artwork, title, artist and genre... it is way faster when i take it out of the array that I made. when i pluck it from the music player, it is very slow
let mySong = myController.nowPlayingItem
songTitle.text = mySong?.title
artistName.text = mySong?.artist
genreTitle.text = mySong?.genre
albumArt.image = mySong?.artwork?.image(at: size)
//Skipping stuff when i choose to skip a song.... This is embedded within a gesture... I don't know if thats whats making it slow but it has about a 1 second lag
myController.skipToNextItem()

Related

GKLeaderboard's localPlayerScore delay after saving a GKScore

I'm trying to get a user's new leaderboard rank after scoring a high score and I found it takes about 5 seconds for the GKLeaderboard scores to update. I've tested the code against a Release build (from Xcode) and the delay is still there.
let score = GKScore(leaderboardIdentifier: leaderboardId)
score.value = Int64(highScore)
GKScore.report([score]) { _ in
// Adding a 5 second delay here solves the problem.
let leaderboard = GKLeaderboard()
leaderboard.identifier = leaderboardId
leaderboard.loadScores { _, _ in
// leaderboard.localPlayerScore shows data from before saving the new score.
}
}
Is there a way around this? The 5-second delay seems flaky. Maybe once the app is in the App Store this delay is no longer there?
I could probably create a workaround by storing the scores before the user plays the game and locally calculate the new rank but the shared code should avoid the need for that, right?

How to use the loop if the track was not started from the beginning (with buffering type = .always in AKPlayer )

I run several AKPlayer with different files and the same length - the first one starts from the beginning, the others start by the button relative to the time of the first. Files are seamless, worth buffering type = .always, loop = true. If AKPlayer did not start from the beginning, then only that part of the track from which the track began to play to the end, and when the loop occurs, starts StartTime is not a zero value. It is necessary that when a loop is played all the tracks from the beginning.
With AKWaveTable, everything works adequately, but there are no pan and pitch settings in the player.
Here is a sample code. In the original, I use an array of players and in a loop I upload all the files to my players. I’m doing something like drummachine - several audio files are playing, and I can turn on / off other files in parallel with respect to the time the player was first started. Each file is the same in duration. When I do the same in AKWaveTable, then everything works correctly, but in the future it does not suit me.
In this example, I run the playPlayer1 method first and after a while I run playPlayer2. When Player1 starts the loop from the beginning, then Player2 starts the loop from the previous currentTime (for example, from the middle of the file) and its length becomes equal forever (endTime - currentTime)
var player1: AKPlayer!
var player2: AKPlayer!
var playersMixer: AKMixer!
init() {
do {
let file1 = try AKAudioFile(readFileName: "Audio/file1.m4a")
let file2 = try AKAudioFile(readFileName: "Audio/file2.m4a")
player1.load(audioFile: file1)
player2.load(audioFile: file2)
} catch {
print(error.localizedDescription)
}
player1.buffering = .always
player1.isLooping = true
player2.buffering = .always
player2.isLooping = true
playersMixer = AKMixer (player1,player2)
AudioKit.output = playersMixer
do {
try AudioKit.start()
} catch {
print(error.localizedDescription)
}
}
func playPlayer1() {
player1.play()
}
func playPlayer2() {
player2.play(from: currentTime)
}
var currentTime: Double {
get {
if player1.isPlaying {
return player1.currentTime
}
return 0
}
}
I think that's a missing feature in AudioKit or a bug. Meanwhile, the current version does use the startTime for the loop points as we can see in the source code (https://github.com/AudioKit/AudioKit/blob/master/AudioKit/Common/Nodes/Playback/Players/Player/AKPlayer.swift)
Meanwhile, there's a solution I found and hope it's useful for other readers:
player.isLooping = false
player.buffering = .dynamic
player.completionHandler = {
player.isLooping = true
player.buffering = .always
player.play(from: 0, to: player.duration)
}
player.play(from: masterPlayer.currentTime)
What I'm doing is to create a player and set .isLooping and .buffering falsely and call .play to run it as a one-time, or a single shot play! A .completionHandler callback is called on complete, where I set .isLooping and .buffering positively. Finally, call .play setting the desired loop points using the properties from and to from self. The masterPlayer refers to a separate player that is used as a reference to get the current play position, I know it's easy to understand but just to avoid assumptions.
EDIT:
After going throw tests, I found an issue that I've reported and deleted before (Is synchronised looping supported for AKPlayers that are multiples in their duration?) and had to reopen, so hopefully, I get an answer and will help understand if it's supposed to work or not.
Also forced player to detach and created a new one:
player.completionHandler = {
player.detach()
let player = self.createPlayer(audioFile: audioFile)
self.connectPlayerToMixer(player)
player.isLooping = true
player.buffering = .always
player.startTime = 0
player.endTime = audioFile.duration
player.play(at: AVAudioTime.now())
}
I've now tested AKWaveTable and hopefully, I did it correctly but it also fails just after the recording that is twice the size of previews recording, for example:
Loop1 > 1 2 3 4
Loop2 > 1 2 3 4
Loop3 > 1 2 3 4 5 6 7 8
Loop4 > 1 2 3 4
Loop5 > 5 6 7 8
(recorded as 1 2 3 4 5 6 7 8, but plays back as 5 6 7 8)
I'll have to go back and test the original AKNodeRecorder to see if the behaviour is similar, but for the moment I'll try another tests with AKWaveTable.
After some time testing * *
Here's the audio recording describing the issue above:
https://drive.google.com/open?id=1zxIJgFFvTwGsve11RFpc-_Z94gEEzql7
After looking at the problem for some time, and all the research I found what seems to be a solution. By shifting the procedure that is used! Instead of setting the player to start along the current time, should instead schedule it to start from the start time. This because the start time is what is used for the loop start and endpoints! To learn a bit more follow the link to another StackOverflow post I've posted before (Solution is presented here (Is synchronised looping supported for AKPlayers that are multiples in their duration?), thanks!

Webaudio :: Play Recorded Audio

I want to play the recorded audio using microphone.
After recording it as 32 bit arrays
let left = e.inputBuffer.getChannelData(0);
let tempLeftChannel = this.state.leftChannel;
tempLeftChannel.push(new Float32Array(left));
this.setState({ leftChannel: tempLeftChannel });
Now In the leftChannel array, I had chunk of audio data. Now, I want to play them in the browser. How can I do that?
You leave quite a bit out from your snippet, but perhaps the following will give you an idea of one way to play out the float array that you have. Let context be the AudioContext that you probably have.
let buffer = new AudioBuffer({length: leftChannel.length,
sampleRate: context.sampleRate});
buffer.copyToChannel(leftChannel, 0);
let source = new AudioBufferSourceNode(context, {buffer: buffer});
source.connect(context.destination);
source.start();

SKAction Sequencing and Grouping Animations

I'm doing some death animations for a game, and wanted to ask for some help. I want my monster to disappear in a puff of smoke, but not before it animates a slash effect going across his body.
I have 3 animations that I want to use:
weaponSlash - a line that draws across the monster. Looks like you slashed him with a sword.
smoke - a puff of smoke that slowly expands out
monsterFalling - the monster falls back, startled
What I want to do is play it in this order:
Simultaneously, the slash appears & the monster starts to fall back
About 0.25s into the above animation, I want the cloud to start to appear
When the cloud is about to end (so maybe after 1s) I want the monster to disappear
Remove the smoke, the monster, the sword, etc, and drop some coins on the ground
I started like this, as a test that works somewhat: (ignore the above times for now)
//Cancel any current actions, like a monster attacking
monster.removeAllActions()
//since you can't play 3 animations on one node at the same time, you have to create 3 separate nodes for everything
let slash = SKSpriteNode()
let cloud = SKSpriteNode()
cloud.size = monster.size
slash.size = monster.size
monster.addChild(cloud)
monster.addChild(slash)
//Start the slash animation
slash.run(self.player.currentlyEquippedWeapon.attackAnimation())
//Start the cloud animation (how I get it is elsewhere and not relevant)
cloud.run(cloudAnimation)
//Run the monster death animation, followed by the cleanup/coin dropping
monster.run(SKAction.sequence([monster.deathAnimation(), SKAction.wait(forDuration: 1), postDeathActions]))
The variable PostDeathActions above simply removes the monster node and animates some coins falling.
WHERE I NEED SOME HELP
So the above code doesn't work so great in that the animations all run independently of each other. Based on this, you can see how regardless of whether the slash/cloud finish, the monster will run two actions: him falling back, followed by cleanup, which just removes the monster and spawns the coins. As you can see I tried to delay this by adding a 1s delay but this is all somewhat of a hack since I may have different monsters or attacks, etc, that are faster/slower. I'd rather guarantee that everything finishes before I despawn the monster.
I tried to group this into an SKAction.Run like so:
let preDeath = SKAction.run {
[unowned self] in
monster.run(monster.deathAnimation()
slash.run(self.player.currentlyEquippedWeapon.attackAnimation())
cloud.run(cloudAnimation)
}
but this runs everything at the same time again.
What I want to do is sequence it like this (pseudo code):
let preDeathAnimations = SKAction.Group([slash, cloud, monsterDeathAnimation])
])
SKAction.sequence([preDeathAnimations, postDeathActions])
So this way it'll run all 3 before running cleanup.
Is there a way to do something like this? I know Sequnce/Group need to be run against an SKNode, but I don't have 3 separate ones.
Thanks for your time reading this and any advice you can offer!
This is one idea that I had, but you could use threading + state + onCompletion blocks to take the math out of it. I didn't test it out fully but this general concept should work:
let slash = SKAction.fadeIn(withDuration: 0.5)
let fall = SKAction.fadeOut(withDuration: 0.25)
let puff = SKAction.fadeIn(withDuration: 0.1)
// Put in ALL of the actions from ALL parties that you want to happen prior to puff:
func findLongestTime(from actions: [SKAction]) -> TimeInterval {
var longestTime = TimeInterval(0)
for action in actions {
if action.duration > longestTime { longestTime = action.duration }
}
// Note, if you put a sequence into this function I don't know if it will work right..
// Might need another func like `findDurationOfSequence(_ sequence: SKAction) -> TimeInterval
return longestTime
}
// Note, if you have the monster doing more than falling prior to puff, then you will
// need to subtract those as well:
let monsterActionsPriorToPuff = [fall]
// Add the duration of all monster animations prior to puff:
var MAPTP_duration = TimeInterval(0)
for action in monsterActionsPriorToPuff {
MAPTP_duration += action.duration
}
// Calculate our final wait time, with no negative numbers:
var waitTime = findLongestTime(from: [slash, fall]) - MAPTP_duration
if waitTime < 0 { waitTime = 0 }
let wait = SKAction.wait(forDuration: waitTime)
// Our monster sequence (I forgot to add the disappear, just add after puff)
let monsterSequence = SKAction.sequence([fall, wait, puff])
// Player slashes:
SKSpriteNode().run(slash)
// Monster will wait 0.25 seconds after falling,
// for slash to finish before puffing:
SKSpriteNode().run(monsterSequence)
et me know if this idea isn't working I can try updating it.

Firebase too slow to load for a tile-based game

I'm building a 2d tile-based game for iOS with swift and Firebase. Because the world is large, I've designed it so that I only subscribe to the tiles that are on screen. That is, instead of adding listeners for all 10,000x10,000 tiles, I add them to just the tiles on screen. As the player moves, I de-register the old listeners and register the new ones. I've added a bit of a buffer zone around the edge of the screen, in the hopes that everything will be sufficiently loaded by the time it moves on screen. Unfortunately, there is often significant enough lag from Firebase that this strategy simply isn't working. On sub-optimal internet connections, it's possible to keep walking into the "unloaded world," taking several seconds at times to load the missing tiles.
Here's the thing, though: other MMO iOS games on the same connection and same device work fine. It's not a terrible connection. Which makes me suspect my implementation, or Firebase itself is at fault.
Fundamentally I'm waiting on the "load once" event for about 20 tiles each time I take a step. A step takes about 1/4 of a second, so each second I'm requesting about 100 items from Firebase. I can't think of a better way, though. Firebase documentation suggests that this should not be a problem, since it's all one socket connection. I could "bucket" the objects into, say, 10x10 blocks which would mean that I'd subscribe to fewer objects, but this would also be more wasteful in terms of total data transfer. If the socket connection is truly optimized, total data transfer should be the only bottleneck, implying this strategy would be wrong.
edit
Here's a video showing how it works. The buffer-size has been reduced to -1, so that you can easily see the edges of the screen and the tiles loading and unloading. Near the end of the video, lag strikes and I wander into the emptiness. I opened up another game and it loaded almost instantly. http://forevermaze.inzania.com/videos/FirebaseLag.mov (n.b., I ended the recording before the screen loaded again. It never fails to load, so it's not as if the code is failing to work. It's pure lag.)
Here is the code I'm using to load the tiles. It's called once for each tile. As I said, this means that this code is called about 20 times per step, in parallel. All other apps are running at a fine speed with no lag. I'm on a MiFi with LTE connectivity in Tokyo, so it's a solid connection.
/**
* Given a path to a firebase object, get the snapshot with a timeout.
*/
static func loadSnapshot(firebasePath: String!) -> Promise<FDataSnapshot?> {
let (promise, fulfill, _) = Promise<FDataSnapshot?>.pendingPromise()
let connection = Firebase(url: Config.firebaseUrl + firebasePath)
connection.observeSingleEventOfType(.Value, withBlock: { snapshot in
if !promise.resolved {
fulfill(snapshot)
}
})
after(Config.timeout).then { () -> Void in
if !promise.resolved {
DDLogWarn("[TIMEOUT] [FIREBASE-READ] \(firebasePath)")
fulfill(nil)
//reject(Errors.network)
}
}
return promise
}
The tiles reside at [ROOT]/tiles/[X]x[Y]. Most tiles contain very little data, but if there are objects on that tile (i.e., other players) those are stored. Here's a screenshot from Firebase:
edit2
Per request, I've recreated this issue very simply. Here is a 100-line XCTestCase class: http://forevermaze.com/code/LagTests.swift
Usage:
Drop the file into your Swift project (it should be stand-alone, requiring only Firebase)
Change the value of firebaseUrl to your root URL (i.e., https://MyProject.firebaseio.com)
Run the testSetupDatabase() function test once to setup the initial state of the database
Run the testWalking() function to test the lag. This is the main test. It will fail if any tile takes longer than 2 seconds to load.
I've tried this test on several different connections. A top-notch office connection passes with no problem, but even a high-end LTE or MiFi connection fails. 2 seconds is already a very long timeout, since it implies that I need to have a 10 tile buffer zone (0.2 seconds * 10 tiles = 2 seconds). Here's some output when I'm connected to a LTE connection, showing that it took nearly 10 seconds (!!) to load a tile:
error: -[ForeverMazeTests.LagTests testWalking] : XCTAssertTrue failed - Tile 2x20 took 9.50058007240295
I ran a few tests and the loading completes in 15-20 seconds when I test over a 3G connection. Over my regular connection it takes 1-2 seconds, so the difference is likely purely based on bandwidth.
I rewrote your test case into a JavaScript version, because I had a hard time figuring out what's going on. Find mine here: http://jsbin.com/dijiba/edit?js,console
var ref = new Firebase(URL);
var tilesPerStep = 20;
var stepsToTake = 100;
function testWalking() {
var startTime = Date.now();
var promises = [];
for (var x=0; x < stepsToTake; x++) {
promises.push(testStep(x));
}
Promise.all(promises).then(function() {
console.log('All '+promises.length+' steps done in '+(Date.now()-startTime)+'ms');
});
}
function testStep(x) {
var result = new Promise(function(resolve, reject){
var tiles = ref.child("/tiles_test");
var loading = 0;
var startTime = Date.now();
console.log('Start loading step '+x);
for (var y=0; y < tilesPerStep; y++) {
loading ++;
tiles.child(x+'x'+y).once('value', function(snapshot) {
var time = Date.now() - startTime;
loading--;
if (loading === 0) {
console.log('Step '+x+' took '+(Date.now()-startTime)+'ms');
resolve(Date.now() - startTime);
}
});
}
});
return result;
}
testWalking();
The biggest difference is that I don't delay starting any of the loading and I don't fail for a specific tile. I think that last bit is why your tests are failing.
All loading from Firebase happens asynchronously, but all requests are going through the same connection. When you start loading, you are queueing up a lot of requests. This timing is skewed by "preceding requests that haven't been fulfilled yet".
This is a sample of a test run with just 10 steps:
"Start loading step 0"
"Start loading step 1"
"Start loading step 2"
"Start loading step 3"
"Start loading step 4"
"Start loading step 5"
"Start loading step 6"
"Start loading step 7"
"Start loading step 8"
"Start loading step 9"
"Step 0 took 7930ms"
"Step 1 took 7929ms"
"Step 2 took 7948ms"
"Step 3 took 8594ms"
"Step 4 took 8669ms"
"Step 5 took 9141ms"
"Step 6 took 9851ms"
"Step 7 took 10365ms"
"Step 8 took 10425ms"
"Step 9 took 11520ms"
"All 10 steps done in 11579ms"
You'll probably note that the time taken for each step does not add up to the time taken for all steps combined. Essentially you are starting each request while there are still requests in the pipeline. This is the most efficient way of loading these items, but it does mean that you'll need to measure performance differently.
Essentially all steps start at almost the same time. Then you're waiting for the first response (which is in the above case includes establishing a WebSocket connection from the client to the correct Firebase server) and after that the responses come in reasonable intervals (given that there are 20 requests per step).
All of this is very interesting, but it doesn't solve your problem of course. I would recommend that you model your data into screen-sized buckets. So instead of having each tile separate, store every 10x10 tiles in a "bucket". You'll reduce the overhead of each separate request and only need to request at most one bucket for every 10 steps.
Update
I'm pretty sure we're just debugging multiple artifacts of your benchmark approach. If I update the code to this:
func testWalking() {
let expectation = expectationWithDescription("Load tiles")
let maxTime = self.timeLimit + self.stepTime * Double(stepsToTake)
let startTime = NSDate().timeIntervalSince1970
for (var x=0; x<stepsToTake; x++) {
let delay = Double(x) * stepTime
let data = ["x":x, "ex": expectation]
stepsRemaining++
NSTimer.scheduledTimerWithTimeInterval(0, target: self, selector: Selector("testStep:"), userInfo: data, repeats: false)
}
waitForExpectationsWithTimeout(maxTime) { error in
let time = NSDate().timeIntervalSince1970 - startTime
print("Completed loading after \(time)")
if error != nil {
print("Error: \(error!.localizedDescription)")
}
}
}
/**
* Helper function to test a single step (executes `tilesPerStep` number of tile loads)
*/
func testStep(timer : NSTimer) {
let tiles = Firebase(url: firebaseUrl).childByAppendingPath("/tiles_test")
let data = timer.userInfo as! Dictionary<String, AnyObject>
let x = data["x"] as! Int
let expectation = data["ex"] as! XCTestExpectation
var loading = 0
print("Start loading \(x)")
for (var y=0; y<tilesPerStep; y++) {
loading++
tiles.childByAppendingPath("\(x)x\(y)").observeSingleEventOfType(.Value, withBlock: { snapshot in
loading--
if loading == 0 {
print("Done loading \(x)")
self.stepsRemaining--
if self.stepsRemaining == 0 {
expectation.fulfill()
}
}
})
}
}
It completes the entire loading in less than 2 seconds over a high-speed network, over 3G it takes between 15 and 25 seconds.
But my recommendation of modeling at a level of more than each single tile remains.