I have 0.2sec long sound file "beep.caf" that I am trying to get to play rapidly. If I set the sounds to play 1 second apart, they play just fine, but if I set them to play any less like 0.8, only the first sound plays. I have the same problem using AudioServicesPlaySystemSound too.
To play the sound twice rapidly I have the following function:
func runAfterDelay(delay: NSTimeInterval, block: dispatch_block_t) {
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC)))
dispatch_after(time, dispatch_get_main_queue(), block)
}
I first setup my sound in the viewDidLoad with the following:
let path = NSBundle.mainBundle().pathForResource("beep", ofType: "caf")!
let url = NSURL(fileURLWithPath: path)
do{
self.startBeep = try AVAudioPlayer(contentsOfURL: url)
self.startBeep.prepareToPlay()
}catch{
print("Audio didn't load")
}
Then when a button is pressed the following code is run:
let start_time:Double = 1.0
let end_time:Double = start_time + 0.8
runAfterDelay(start_time ) {
self.startBeep.play()
}
runAfterDelay(end_time ) {
self.startBeep.play()
}
I have tried to stop the sound before the next play, but that doesn't help either. I have even duplicated the should file (beep & beepStop) to see if it had something to do with playing the same sound file and I get the same problems.
Any ideas??
The problem is with the time and not the players. The delay time was supposed to be in a 10th of a millisecond but was a 100th of a millisecond (.1 vs .01) which was so short of time between beeps that it often didn't fire accurately. A false positive was created when the time was set to 1.0 seconds.
Related
We've been working with AudioUnits in Core Audio. It is simultaniously a very powerful audio framework, and one of the worst documented which makes it both a joy and a frustration to work with.
We want to accomplish something we know iPads had been able to do since iOS 6.0 - Multiple audio inputs.
So far - from the 2012 Developer Talk - It appears you have to set the audio session to MultiRoute. We've done this. If I plug in an a soundcard from a keyboard. I can see that there are two inputs. Great. We're then told that we need to set a ChannelMap on a Remote I/O unit.
To what? Well... here's where it gets vague. We need to set all the channels we don't want to -1 and the channels we want to 0 and 1 (for stereo input or for mono?).
We attempt this and... nothing. Sound still plays through on the 'last in wins' principle. Microphone if everything plugged out, soundcard if that's the one plugged in. But we can't switch between them.
This setup code is always run before the other function listed
func setupAudioSession() {
self.audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSessionCategoryMultiRoute, with: [.mixWithOthers])
try audioSession.setActive(true)
audioSessionWasSetup = true
} catch let error {
//TODO: Implement something here
print(error)
audioSessionWasSetup = false
}
}
We then have a remote I/O with an associated audiograph set up. This has been tested and works beautifully. But we need to be able to set where it's pulling sound from.
I've attempted to do it with this, but not only doesn't it have any effect... nothing happens.
Am I missing something?
private func setChannelMap(onAudioUnit audioUnit: AudioUnit?, toChannel channelIndex: Int = 0) {
var channelMap: [Int32] = []
if audioUnit == nil {
return
}
var numberOfInputChannels: UInt32 = 4 // Two stereo inputs? - I'm just guessing here
let mapSize: UInt32 = numberOfInputChannels * UInt32(MemoryLayout<Int32>.size);
for _ in 0...(numberOfInputChannels) {
channelMap.append(-1)
}
channelMap[2 * channelIndex] = 0;
channelMap[2 * channelIndex + 1] = 1;
let status = AudioUnitSetProperty(audioUnit!,
kAudioOutputUnitProperty_ChannelMap,
kAudioUnitScope_Input,
0,
&channelMap,
mapSize);
self.checkError(status, "Failed to set Channel Map on input unit")
}
There isn't any documentation on this at all as far as I've been able to find. Nor any code examples.
I hope you can help us.
I'm just getting started with TVOS and was wondering if anyone has addressed looping in TVOS / TVJS / TVML. I'm using this tutorial to create a simple interface for playing videos. My wrinkle is that I want these videos to play in a continuous seamless loop - basically a moving screensaver type effect - see example videos at art.chrisbaily.com.
Is there a simple way to do this, or do I need to build some kind of event listener to do the looping manually? I'd like the videos to be fairly hi res, and the videos would be somewhere between 1 and 3 minutes.
I was looking for an answer for this question as well. And found that the only way this would be possible is to create an event listener and adding a duplicate media item to the playlist. But honestly it is not that hard, given if you followed the tutorial that you listed in your post.
So the code would be something like
player.playlist = playlist;
player.playlist.push(mediaItem);
//Again push the same type of media item in playlist so now you have two of the same.
player.playlist.push(mediaItem);
player.present();
This will make sure that once your first video ends the second one starts playing which is essentially a loop. Then for the third one and on you implement an event listener using "mediaItemWillChange" property. This will make sure that once a video ends a new copy of the same video is added to the playlist.
player.addEventListener("mediaItemWillChange", function(e) {
player.playlist.push(mediaItem);
});
Just put the event listener before you start the player using
player.present();
Note this question and idea of this sort was already asked/provided on Apple's discussion board. I merely took the idea and had implemented it in my own project and now knowing that it works I am posting the solution. I found that if I pop the first video out of the playlist and add then push a new on the playlist as mentioned in the linked thread below, my videos were not looping. Below is the link to the original post.
How to repeat video with TVJS Player?
You could also set repeatMode on the playlist object
player.playlist.repeatMode = 1;
0 = no repeat
1 = repeat all items in playlist
2 = repeat current item
There's really no need to push a second media item onto the. Simply listen for the media to reach its end, then set the seek time back to zero. Here's the code.
Play the media.
private func playVideo(name: String) {
guard name != "" else {
return
}
let bundle = NSBundle(forClass:object_getClass(self))
let path = bundle.pathForResource(name, ofType:"mp4")
let url = NSURL.fileURLWithPath(path!)
self.avPlayer = AVPlayer(URL: url)
if (self.avPlayerLayer == nil) {
self.avPlayerLayer = AVPlayerLayer(player: self.avPlayer)
self.avPlayerLayer.frame = previewViews[1].frame
previewViews[1].layer.addSublayer(self.avPlayerLayer)
avPlayer.actionAtItemEnd = .None
//Listen for AVPlayerItemDidPlayToEndTimeNotification
NSNotificationCenter.defaultCenter().addObserver(self, selector: "playerItemDidReachEnd:", name: AVPlayerItemDidPlayToEndTimeNotification, object: self.avPlayer.currentItem)
}
avPlayer.play()
}
Play Again
func playerItemDidReachEnd(notification: NSNotification) {
let item = notification.object as? AVPlayerItem
item?.seekToTime(kCMTimeZero)
}
My app uses multiple threads of NSTimer Object.
Within the one function (called randomly at random times, not a fixed delay) I want it to pause the whole app, pause the threads for 1 second only. I have the following code:
[self performSelector:#selector(subscribe) withObject:self afterDelay:3.0 ];
Which is objective C, and I tried translating it into Swift like this:
self.performSelector(Selector:changeColourOfPage(), withObject: self, afterDelay: 1.0)
But I am getting the error Missing argument for parameter 'waitUntilDone' in call and when I put that in, it says it wants the argument modes but when I put that in it says Extra argument modes.
I cannot figure out how to pause the app and all it's threads for a couple of seconds, and then carry on like normal?
Any ideas?
The performSelector methods aren't available in Swift. You can get the delay functionality by using dispatch_after.
let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_SEC * 1))
dispatch_after(delayTime, dispatch_get_main_queue()){
changeColourOfPage()
}
Another way in Swift 3.1
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
changeColourOfPage()
}
You can substitute other units like .milliseconds(1000) or .microseconds(1_000_000) or .nanoseconds(1_000_000_000) for the time interval.
Sorry to answer an old question, but just came along a better way to do this -
import Darwin //Put this line at the beginning of your file
func pauseForAMoment() {
sleep(1)
changeColorOfPage()
}
I am using an AVQueuedPlayer to playback SoudCloud files. If I use seekToTime:(CMTime)time on the AVQueuedPlayer the player just jumps to 0 and starts playing again. Any ideas how to fix this?
I tried to paste the MP3 URL into other players and all of them can seek, so it does not seem to be an server issue.
Bests,
Philip
OK to make this clear: You should use something like this:
- (void)advanceToTime:(float)timeToGo {
int32_t timeScale = self.audioPlayer.currentItem.asset.duration.timescale;
CMTime time = CMTimeMakeWithSeconds(timeToGo, timeScale);
[self.audioPlayer seekToTime:time completionHandler:^(BOOL finished) {
NSLog(#"Finished %#",finished ? #"NO" : #"YES");
}];
}
I'm currently writing a small application which takes a folder containing many short video files (~1mn each) and plays them like they were ONE long video file.
I've been using AVQueuePlayer to play them all one after another but I was wondering if there were an alternative to this, because I'm running into some problems:
there is a small but noticeable gap when the player switches to the next file
I can't go back to the previous video file without having to remove all the items in the queue and put them back
I'd like to be able to go to any point in the video, just as if it were a single video file. Is AVPlayer the best approach for this?
I realize that it's been about 6 years since this was asked, but I found a solution to this shortly after seeing this question and maybe it will be helpful to someone else.
Instead of using a an AVQueuePlayer, I combined the clips in an AVMutableComposition (a subclass of AVAsset) which I could then play in a normal AVPlayer.
let assets: [AVAsset] = urlsOfVideos.map(AVAsset.init)
let composition = AVMutableComposition()
let compositionVideoTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
let compositionAudioTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
var insertTime = CMTime.zero
for asset in assets {
let range = CMTimeRange(start: .zero, duration: asset.duration)
guard let videoTrack = asset.tracks(withMediaType: .video).first,
let audioTrack = asset.tracks(withMediaType: .audio).first else {
continue
}
compositionVideoTrack?.preferredTransform = orientation!
try? compositionVideoTrack?.insertTimeRange(range, of: videoTrack, at: insertTime)
try? compositionAudioTrack?.insertTimeRange(range, of: audioTrack, at: insertTime)
insertTime = CMTimeAdd(insertTime, asset.duration)
}
Then you create the player like this
let player = AVPlayer(playerItem: AVPlayerItem(asset: composition))