How to skip ahead n seconds while playing track using web audio API? - 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.

Related

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);

Change duration of SKAction playSoundFileNamed

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

Specify starting time of track within URL for SoundCloud?

Anyone know if there's a way to generate URL's for SoundCloud tracks that specify a start time for the song? I'm looking for a way to force playback of streams at a certain time in the stream without having to do any processing on my end via the API.
As #bsz correctly noticed, we have released a way of specifying start time on the sound when linking to it, append #t=12s to the sound's URL to start it at 12th second, etc.
If the audio is long enough, you can use (e.g.) #t=2h10m12s.
They seem to have added a #t option but not sure if you can also give an stop time:
https://soundcloud.com/razhavaniazha-com/06-06-2013-razhavaniazha-afn#t=54:00
You can also specify a start time one minute or over by appending
#XmXs
If you want a time right on the minute mark (e.g. 3:00), include 0 seconds (0s) in the time code (i.e. 3m0s), or else the start time will be ignored. It does not appear to support a start time over an hour.
Click share and set or pick time

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!