Change duration of SKAction playSoundFileNamed - sprite-kit

How do i change the duration of a created sound created with SKAction.playSoundFileNamed:
var sound = SKAction.playSoundFileNamed("sound.mp3", waitForCompletion: false)
I tried setting sound.duration = 1.0 but with no luck so far ?

In short, that is impossible with SKAction.playSoundFileNamed: method.
duration property specifies the time in seconds required for current action to complete.
When using SKAction.playSoundFileNamed: method you don't have control over the audio being played (no pausing, stoping, resuming pitching etc). It's meant to play a sound, nothing more.
Some useful links:
https://stackoverflow.com/a/22590464/3402095
https://stackoverflow.com/a/21023417/3402095

Related

How to skip ahead n seconds while playing track using web audio API?

Using Web Audio API, I'm trying to build an mp3 player with a "Skip ahead 15 seconds" feature.
I'm able to load an mp3 using a source buffer, and can get it to start playing. I want to do something like this, though I know currentTime is not a settable property:
context.currentTime += 15
How do you skip forward n seconds once the song is already playing?
Unfortunately there is no single API call to achieve the desired effect but it's doable. Every AudioBufferSourceNode can only be used once which is why we have to create a new one in order to change something.
Let's imagine we have two variables coming from somewhere called audioContext and audioBuffer. In addition we define two more variables to store the initial startTime and the currently running AudioBufferSourceNode.
let audioBufferSourceNode;
let startTime;
The first time we play the audioBuffer we play it directly from the start. The only special thing here is that we keep a reference to the audioBufferSourceNode and that we remember the startTime.
audioBufferSourceNode = audioContext.createBufferSource();
audioBufferSourceNode.buffer = audioBuffer;
audioBufferSourceNode.connect(audioContext.destination);
startTime = context.currentTime;
audioBufferSourceNode.start(startTime);
If you want to skip ahead some time later the previously started audioBufferSourceNode needs to be stopped first.
const currentTime = context.currentTime;
audioBufferSourceNode.stop(currentTime);
audioBufferSourceNode.disconnect();
In addition a new one needs to be created by reusing the same audioBuffer as before. The only difference here is that we apply an offset to make sure it skips 15 seconds ahead.
audioBufferSourceNode = audioContext.createBufferSource();
audioBufferSourceNode.buffer = audioBuffer;
audioBufferSourceNode.connect(audioContext.destination);
audioBufferSourceNode.start(currentTime, currentTime - startTime + 15);
To be prepared to skip another time it's necessary to update the startTime.
startTime -= 15;
This is of course an oversimplified example. In reality there should be a check to make sure that there is enough audio data left to skip ahead. You could also apply a little fade-in/out when skipping to avoid click sounds. ... This is only meant to illustrate the general idea.

Looping AVPlayer with custom Start/End Times

I am using an AVPlayer to play video. I would like to be able to loop sections of the video based on the user's input (while the video is playing, the user can press a button to start a loop and then press it once more to end after a few seconds pass -- then it should begin playing at the start time and continue to loop once the current time reaches the specified end time)
I can get these start/end loop times just by getting the player's currentTime
var startLoop : CMTime = player.currentTime()
// seconds pass by ....
var endLoop : CMTime = player.currentTime()
I know there is a way to cleanly loop the video back to the beginning once it has finished playing like so:
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.player.currentItem, queue: .main) { [weak self] _ in
self?.player?.seek(to: CMTime.zero)
self?.player?.rate = self?.rate ?? 1.0
}
I was wondering if there is a way to do this with my custom startLoop and endLoop times?
There are a few ways to do looping in AVFoundation. The simple way is like you described by listening to the notification and then calling seek(to: startLoop). you can use addBoundaryTimeObserver to listen for specific time.
https://developer.apple.com/documentation/avfoundation/avplayer/1388027-addboundarytimeobserver
However, a more advanced way to do looping is to try to add a Key Value Observer to AVQueuePlayer and try inserting 2 copies of an AVPlayerItem with the specific range of the looping asset you want. Then you can use looping techniques such as the treadmill technique or AVPlayerLooper. The key to creating custom time ranged assets is to use an AVMutableComposition and insertTimeRange.
Also, if you really want to be experimental, you can try a 3rd option which is possibly using an AVMutableComposition as your original source asset and start mutating its underlying tracks on the fly while it is playing. I've tried something similar and it can work if you are not manipulating time ranges that are being played. However, there is no docs on this approach and Apple may change code in future versions that may break this.
References:
https://developer.apple.com/videos/play/wwdc2016/503/
https://developer.apple.com/library/archive/samplecode/avloopplayer/Introduction/Intro.html
https://developer.apple.com/library/archive/samplecode/AVCustomEdit/Introduction/Intro.html
Recommended way of updating timeRange property on AVPlayerLooper
#spitchay reach out to me if you have other AVFoundation questions.

HTML5 Audio: Setting currentTime and then playing on iPhone spontaneously rewinds 50-100 milliseconds

I'm attempting to play audio from a specific location inside a file while also receiving updates on the playback using the onTimeUpdate event. I examine the currentTime property within the onTimeUpdate handler so that I can provide visual feedback on where we are in the file.
My problem is that when I set currentTime to a known value and then call play(), the first time I get the currentTime property in my onTimeUpdate handler, it will be as I set it. But the next time onTimeUpdate is called, it's actually a smaller value than I originally started playing from, sometimes more than 100 ms smaller!
For example:
player.currentTime = 2.0;
player.play();
Then, outputting player.currentTime from within my onTimeUpdate handler:
2
1.95 // WTF??
2.24
2.56
...
The problem is that this causes my UI widgets that indicate the current location in the track to jump around. I have a clunky workaround that I am implementing, but I'd like to know if there is any known way to stop this from happening, or if I'm just not doing something right.
To my knowledge, it only happens on iPhone. I'm testing with an iPhone 6 and iOS 10.1.1.
Here's a demonstration if you'd like to see this for yourself.
http://codepen.io/brianrak/pen/7db413c4431827fe3318e99ae497cf2a?editors=1010
Thanks in advance!
var second = ("0" + parseInt(player.currentTime % 60)).slice(-2);
var minutes = ("0" + parseInt((player.currentTime / 60) % 60)).slice(-2);

Synchronize the playback of two or more AVAudioPlayer in Iphone

I need to play 2 sounds with 2 AVAudioPlayer objects at the same exact time... so I found this example on Apple AVAudioPlayer Class Reference (https://developer.apple.com/library/mac/#documentation/AVFoundation/Reference/AVAudioPlayerClassReference/Reference/Reference.html):
- (void) startSynchronizedPlayback {
NSTimeInterval shortStartDelay = 0.01; // seconds
NSTimeInterval now = player.deviceCurrentTime;
[player playAtTime: now + shortStartDelay];
[secondPlayer playAtTime: now + shortStartDelay];
// Here, update state and user interface for each player, as appropriate
}
What I don't understand is: why also the secondPlayer has the shorStartDelay?
Shouldn't it be without? I thought the first Player needed a 0.1 sec delay as it is called before the second Player... but in this code the 2 players have the delay...
Anyone can explain me if that is right and why?
Thanks a lot
Massy
If you only use the play method ([firstPlayer play];), firstPlayer will start before the second one as it will receive the call before.
If you set no delay ([firstPlayer playAtTime:now];), the firstPlayer will also start before de second one because firstPlayer will check the time at which it is supposed to start, and will see that it's already passed. Thus, it will have the same behaviour as when you use only the play method.
The delay is here to ensure that the two players start at the same time. It is supposed to be long enough to ensure that the two players receive the call before the 'now+delay' time has passed.
I don't know if I'm clear (English is not my native langage). I can try to be more clear if you have questions
Yeah what he said ^ The play at time will schedule both players to start at that time (sometime in the future).
To make it obvious, you can set "shortStartDelay" to 2 seconds and you will see there will be a two second pause before both items start playing.
Another tip to keep in mind here are that when you play/pause AVAudioPlayer they dont actually STOP at exactly the same time. So when you want to resume, you should also sync the audio tracks.
Swift example:
let currentDeviceTime = firstPlayer.deviceCurrentTime
let trackTime = firstPlayer.currentTime
players.forEach {
$0.currentTime = trackTime
$0.play(atTime: currentDeviceTime + 0.1)
}
Where players is a list of AVAudioPlayers and firstPlayer is the first item in the array.
Notice how I am also resetting the "currentTime" which is how many seconds into the audio track you want to keep playing. Otherwise every time the user plays/pauses the track they drift out of sync!

In IOS core audio, how do you find the true current play head position of a file player audio unit?

I have a program that uses a file player audio unit to play, pause and stop a audio file. The way I am accomplishing this is by initializing the file player audio unit to play the file at position zero and then when the user presses the pause button, I stop the AUGraph, capture the current position, and then use that position as the start position when the user presses the play button. Everything is working as it should, but every 3 or 4 times I hit pause and then play, the song starts playing a half to a full second BEFORE the point where I hit pause.
I can't figure out why this is happening, do any of you have any thoughts? here is a simplified version of my code.
//initialize AUGraph and File player Audio unit
...
...
...
//Start AUGraph
...
...
...
// pause playback
- (void) pauseAUGraph {
//first stop the AuGrpah
result = AUGraphStop (processingGraph);
// get current play head position
AudioTimeStamp ts;
UInt32 size = sizeof(ts);
result = AudioUnitGetProperty(filePlayerUnit,
kAudioUnitProperty_CurrentPlayTime, kAudioUnitScope_Global, 0, &ts,
&size);
//save our play head position for use later
//must add it to itself to take care of multiple presses of the pause button
sampleFrameSavedPosition = sampleFrameSavedPosition + ts.mSampleTime;
//this stops the file player unit from playing
AudioUnitReset(filePlayerUnit, kAudioUnitScope_Global, 0);
NSLog (#"AudioUnitReset - stopped file player from playing");
//all done
}
// Stop playback
- (void) stopAUGraph {
// lets set the play head to zero, so that when we restart, we restart at the beginning of the file.
sampleFrameSavedPosition = 0;
//ok now that we saved the current pleayhead position, lets stop the AUGraph
result = AUGraphStop (processingGraph);
}
May be you should use packet counts instead of timestamps, since you just want to pause and play the music, not display the time information.
See BufferedAudioPlayer for an example of using this method.
It may be due to rounding problems with your code:
For example, if every time you hit the pause button, your timer would record at a 0.5/4 seconds before your actual pause time, you would still see a desired result. But after repeating for four more times, the amount of space you have created is 0.5/4 times 4 which is the half of a second you seem to be experiencing.
Thus, I would pay careful attention to the object types you are using and make sure they don't round inappropriately. Try using a double float for your sample times to try to alleviate that problem!
Hope this is clear and helpful! :)