Best ways to play simple sound effect in iOS - iphone

I'm finding a number of conflicting data about playing sounds in iOS. What is a recommended way to play just a simple "ping" sound bite every time the user touches the screen?

I use this:
Header file:
#import <AudioToolbox/AudioServices.h>
#interface SoundEffect : NSObject
{
SystemSoundID soundID;
}
- (id)initWithSoundNamed:(NSString *)filename;
- (void)play;
#end
Source file:
#import "SoundEffect.h"
#implementation SoundEffect
- (id)initWithSoundNamed:(NSString *)filename
{
if ((self = [super init]))
{
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:filename withExtension:nil];
if (fileURL != nil)
{
SystemSoundID theSoundID;
OSStatus error = AudioServicesCreateSystemSoundID((__bridge CFURLRef)fileURL, &theSoundID);
if (error == kAudioServicesNoError)
soundID = theSoundID;
}
}
return self;
}
- (void)dealloc
{
AudioServicesDisposeSystemSoundID(soundID);
}
- (void)play
{
AudioServicesPlaySystemSound(soundID);
}
#end
You will need to create an instance of SoundEffect and direct call the method play on it.

This is the best way of playing a simple sound in iOS (no more than 30 seconds):
//Retrieve audio file
NSString *path = [[NSBundle mainBundle] pathForResource:#"soundeffect" ofType:#"m4a"];
NSURL *pathURL = [NSURL fileURLWithPath : path];
SystemSoundID audioEffect;
AudioServicesCreateSystemSoundID((__bridge CFURLRef) pathURL, &audioEffect);
AudioServicesPlaySystemSound(audioEffect);
// call the following function when the sound is no longer used
// (must be done AFTER the sound is done playing)
AudioServicesDisposeSystemSoundID(audioEffect);

(Small amendment to the correct answer to take care of the disposing of the audio)
NSString *path = [[NSBundle mainBundle] pathForResource:#"soundeffect" ofType:#"m4a"];
NSURL *pathURL = [NSURL fileURLWithPath : path];
SystemSoundID audioEffect;
AudioServicesCreateSystemSoundID((__bridge CFURLRef) pathURL, &audioEffect);
AudioServicesPlaySystemSound(audioEffect);
// Using GCD, we can use a block to dispose of the audio effect without using a NSTimer or something else to figure out when it'll be finished playing.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(30 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
AudioServicesDisposeSystemSoundID(audioEffect);
});

You can use AVFoundation or AudioToolbox.
Here're two examples which use the libraries separately.
SAMSoundEffect (archived repository)
BRYSoundEffectPlayer

Here's an updated answer for Swift (4):
import AudioToolbox
func playSound() {
var soundId: SystemSoundID = 0
guard let soundPath = Bundle.main.url(forResource: "Success7", withExtension: "wav") else {
print("Error finding file")
return
}
let error = AudioServicesCreateSystemSoundID(soundPath as CFURL, &soundId)
if error != kAudioServicesNoError {
print("Error loading sound")
return
}
AudioServicesPlaySystemSoundWithCompletion(soundId) {
AudioServicesDisposeSystemSoundID(soundId)
}
}
If you have a sound effect that you want to play multiple times in a view, then you can be a little more smart about loading and disposing of the audio:
class YourViewController: UIViewController {
fileprivate lazy var soundId: SystemSoundID? = {
guard let soundPath = Bundle.main.url(forResource: "Success7", withExtension: "wav") else {
return nil
}
var soundId: SystemSoundID = 0
let error = AudioServicesCreateSystemSoundID(soundPath as CFURL, &soundId)
if error != kAudioServicesNoError {
return nil
}
return soundId
}()
func playScannedSound() {
guard let soundId = self.soundId else {
return
}
AudioServicesPlaySystemSoundWithCompletion(soundId, nil)
}
deinit {
guard let soundId = self.soundId else {
return
}
AudioServicesDisposeSystemSoundID(soundId)
}
}

Related

how can i check if NSUrl is a valid image url?

I'm using SDWebImage library and I have this code:
[cell.imgLogo setImageWithURL:[NSURL URLWithString:[item objectForKey:#"s_logo"]] placeholderImage:[UIImage imageNamed:#"default.png"]];
I have tweak the library SDWebImage a little bit to ignore empty string or a NSUrl with empty string in method downloadWithURL: delegate: options: userInfo::
if ([url isKindOfClass:NSString.class])
{
if ([(NSString *)url length] > 0) {
url = [NSURL URLWithString:(NSString *)url];
} else {
return;
}
}
else if (![url isKindOfClass:NSURL.class])
{
url = nil; // Prevent some common crashes due to common wrong values passed like NSNull.null for instance
}
else if ([url isKindOfClass:NSURL.class]) {
if ([[url absoluteString] length] > 0) {
//valid url
} else {
return;
}
}
So now it works with empty string and just to display its default image but the problem is when it comes to a string that is not an image url like:
http://beta.xxxxxxx.com/gangnamwe?to=boko
It displays nothing, it removes the placeholder image and displays nothing.
So how will I identify a valid image url? or is there any better work around for this?
Your help are much appreaciated.
you can check after getting NSData from NSURL . You can use GCD to download data
here is an example i created which save your image in photo library.
dispatch_async(dispatch_queue_create("com.getImage", NULL), ^(void) {
NSData *data=[NSData dataWithContentsOfURL:[NSURL URLWithString:#"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRKII9COB-hvMef4Zvb9XYVbXKDFZHJAHwwzzGyMiy_b-q65GD43Chd37jH"]];
UIImage *image=[UIImage imageWithData:data];
if (image==nil) {
//yourImageURL is not valid
image=[UIImage imageNamed:#"placeholder.png"];
}
else{
//yourImageURL is valid
dispatch_async(dispatch_get_main_queue(), ^{
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
//show your image
});
}
});
There is method in NSURL to check if the file exsists.
- (BOOL)checkResourceIsReachableAndReturnError:(NSError **)error
Example
NSURL *theURL = [NSURL URLWithString:string];
NSError *err;
if ([theURL checkResourceIsReachableAndReturnError:&err] == NO)
{
NSLog(#"resource not reachable");
}
Here's a category on NSURL for you :
// nsurl+documentTypes.h
#interface NSURL (documentTypes)
- (BOOL)isImageType;
#end
// nsurl+documentTypes.m
#implementation NSURL (documentTypes)
- (BOOL)isImageType
{
NSString * UTI = (__bridge NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension,(__bridge CFStringRef)[self pathExtension],NULL);
return UTTypeConformsTo((__bridge CFStringRef)UTI, kUTTypeImage);
}
#end
You can check if the url ends with some image name or not.You can get the parts of the url in following way:
NSURL* url = [NSURL URLWithString:#"http://digg.com/news/business/24hr"];
NSString* reducedUrl = [NSString stringWithFormat:
#"%#://%#/%#",
url.scheme,
url.host,
[url.pathComponents objectAtIndex:1]];
Now, take the last object of the pathComponents and check if it contains .png or .jpg etc.
In Swift:
import Foundation
public extension NSURL {
public var isImage: Bool {
return UTI.map{ UTTypeConformsTo($0, kUTTypeImage) } ?? false
}
public var UTI: String? {
var value: AnyObject?
let _ = try? getResourceValue(&value, forKey: NSURLTypeIdentifierKey)
return value as? String
}
}
e.g:
let url = NSURL(fileURLWithPath: "/Users/i/Desktop/image.png")
url.isImage //--> true

How can you embed youtube videos that don't start the external player on Iphone?

I'm trying to implement an embedded Youtube video in my application.
The only way I've managed to do this is with frameworks like LBYouTubeView or HCYotubeParser. From what I've read they are against the Youtube TOS because they basically strip the video link from the http getting something like this.
This indeed can be played inside a MPMoviePlayerController without any problems (and without leaving the application).
From what I've search there are two applications that managed to do this Vodio and Frequency so I'm wondering if there is a special sdk for developers or an affiliate program I might have missed.
From what I also read Youtube Engineers respond to this questions if they are tagged with youtube-api so I hope you could clear this up.
I've read about the UIWebView implementations but that scenario actually opens the an external video player which I have no control over.
I've looked around for a while and this is what I came up with:
It involves adding a youtube.html file to your project with the following code:
<html>
<head><style>body{margin:0px 0px 0px 0px;}</style></head>
<body>
<!-- 1. The <iframe> (and video player) will replace this <div> tag. -->
<div id="player"></div>
<script>
// 2. This code loads the IFrame Player API code asynchronously.
var tag = document.createElement('script');
tag.src = "http://www.youtube.com/player_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// 3. This function creates an <iframe> (and YouTube player)
// after the API code downloads.
var player;
function onYouTubePlayerAPIReady()
{
player = new YT.Player('player',
{
width: '640',
height: '360',
videoId: '%#',
playerVars: {'autoplay' : 1, 'controls' : 0 , 'vq' : 'hd720', 'playsinline' : 1, 'showinfo' : 0, 'rel' : 0, 'enablejsapi' : 1, 'modestbranding' : 1},
events:
{
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
// 4. The API will call this function when the video player is ready.
function onPlayerReady(event)
{
event.target.playVideo();
}
// 5. The API calls this function when the player's state changes.
// The function indicates that when playing a video (state=1),
// the player should play for six seconds and then stop.
var done = false;
function onPlayerStateChange(event)
{
if (event.data == YT.PlayerState.ENDED)
{
window.location = "callback:anything";
};
}
function stopVideo()
{
player.stopVideo();
window.location = "callback:anything";
}
function getTime()
{
return player.getCurrentTime();
}
function getDuration()
{
return player.getDuration();
}
function pause()
{
player.pauseVideo();
}
function play()
{
player.playVideo();
}
</script>
</body>
</html>
Also you have to create a UiWebView subclass that is also a delegate for UIWebViewDelegate
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self == nil) return nil;
self.mediaPlaybackRequiresUserAction = NO;
self.delegate = self;
self.allowsInlineMediaPlayback = TRUE;
self.userInteractionEnabled = FALSE;
return self;
}
- (void)loadVideo:(NSString *)videoId
{
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"YouTube" ofType:#"html"];
// if (filePath == nil)
NSError *error;
NSString *string = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error];
// TODO: error check
string = [NSString stringWithFormat:string, videoId];
NSData *htmlData = [string dataUsingEncoding:NSUTF8StringEncoding];
// if (htmlData == nil)
NSString *documentsDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *targetPath = [documentsDirectoryPath stringByAppendingPathComponent:#"youtube2.html"];
[htmlData writeToFile:targetPath atomically:YES];
[self loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:targetPath]]];
File = 0;
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if ([[[request URL] scheme] isEqualToString:#"callback"])
{
Playing = FALSE;
NSString *documentsDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *targetPath = [documentsDirectoryPath stringByAppendingPathComponent:#"youtube2.html"];
NSError *error;
[[NSFileManager defaultManager] removeItemAtPath:targetPath error:&error];
}
return YES;
}
Basically it creates a UIWebView and loads the code in the youtube.html file. Because the youtube.html is static and I need to load a certain id I create a copy on the fly in the documents folder youtube2.html in which I add the string id.
The whole thing is that [self loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:targetPath]]]; works but loading a NSUrlRequest from a string doesn't.
The Javascript functions you see in the html file are for controlling the video. If you need access to time or full duration you can get them like this:
float VideoDuration = [[YT_WebView stringByEvaluatingJavaScriptFromString:#"getDuration()"] floatValue];
float VideoTime = [[YT_WebView stringByEvaluatingJavaScriptFromString:#"getTime()"] floatValue];

Replay items in AVQueuePlayer after last

I need to create something like an infinite loop in my AVQueuePlayer. Especially, I want to replay the whole NSArray of AVPlayerItems once the last component finishes playing.
I must admit that I actually do not have any idea how to achieve this and hope you can give me some clues.
best way to loop a sequence of videos in AVQueuePlayer.
observe for each playeritem in AVQueuePlayer.
queuePlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;
for(AVPlayerItem *item in items) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(nextVideo:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:item ];
}
on each nextVideo insert the currentItem again to queue it for playback. make sure to seek to zero for each item. after advanceToNextItem the AVQueuePlayer will remove the currentItem from queue.
-(void) nextVideo:(NSNotification*)notif {
AVPlayerItem *currItem = notif.userInfo[#"object"];
[currItem seekToTime:kCMTimeZero];
[queuePlayer advanceToNextItem];
[queuePlayer insertItem:currItem afterItem:nil];
}
This is it pretty much from scratch. The components are:
Create a queue that is an NSArray of AVPlayerItems.
As each item is added to the queue, set up a NSNotificationCenter observer to wake up when the video reaches the end.
In the observer's selector, tell the AVPlayerItem that you want to loop to go back the the beginning, then tell the player to play.
(NOTE: The AVPlayerDemoPlaybackView comes from the Apple "AVPlayerDemo" sample. Simply a subclass of UIView with a setter)
BOOL videoShouldLoop = YES;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSMutableArray *videoQueue = [[NSMutableArray alloc] init];
AVQueuePlayer *mPlayer;
AVPlayerDemoPlaybackView *mPlaybackView;
// You'll need to get an array of the files you want to queue as NSARrray *fileList:
for (NSString *videoPath in fileList) {
// Add all files to the queue as AVPlayerItems
if ([fileManager fileExistsAtPath: videoPath]) {
NSURL *videoURL = [NSURL fileURLWithPath: videoPath];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL: videoURL];
// Setup the observer
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(playerItemDidReachEnd:)
name: AVPlayerItemDidPlayToEndTimeNotification
object: playerItem];
// Add the playerItem to the queue
[videoQueue addObject: playerItem];
}
}
// Add the queue array to the AVQueuePlayer
mPlayer = [AVQueuePlayer queuePlayerWithItems: videoQueue];
// Add the player to the view
[mPlaybackView setPlayer: mPlayer];
// If you should only have one video, this allows it to stop at the end instead of blanking the display
if ([[mPlayer items] count] == 1) {
mPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;
}
// Start playing
[mPlayer play];
- (void) playerItemDidReachEnd: (NSNotification *)notification
{
// Loop the video
if (videoShouldLoop) {
// Get the current item
AVPlayerItem *playerItem = [mPlayer currentItem];
// Set it back to the beginning
[playerItem seekToTime: kCMTimeZero];
// Tell the player to do nothing when it reaches the end of the video
// -- It will come back to this method when it's done
mPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;
// Play it again, Sam
[mPlayer play];
} else {
mPlayer.actionAtItemEnd = AVPlayerActionAtItemEndAdvance;
}
}
That's it! Let me know of something needs further explanation.
I figured out a solution to loop over all the videos in my video queue, not just one. First I initialized my AVQueuePlayer:
- (void)viewDidLoad
{
NSMutableArray *vidItems = [[NSMutableArray alloc] init];
for (int i = 0; i < 5; i++)
{
// create file name and make path
NSString *fileName = [NSString stringWithFormat:#"intro%i", i];
NSString *path = [[NSBundle mainBundle] pathForResource:fileName ofType:#"mov"];
NSURL *movieUrl = [NSURL fileURLWithPath:path];
// load url as player item
AVPlayerItem *item = [AVPlayerItem playerItemWithURL:movieUrl];
// observe when this item ends
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:item];
// add to array
[vidItems addObject:item];
}
// initialize avqueueplayer
_moviePlayer = [AVQueuePlayer queuePlayerWithItems:vidItems];
_moviePlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;
// create layer for viewing
AVPlayerLayer *layer = [AVPlayerLayer playerLayerWithPlayer:_moviePlayer];
layer.frame = self.view.bounds;
layer.videoGravity = AVLayerVideoGravityResizeAspectFill;
// add layer to uiview container
[_movieViewContainer.layer addSublayer:layer];
}
When the notification is posted
- (void)playerItemDidReachEnd:(NSNotification *)notification {
AVPlayerItem *p = [notification object];
// keep playing the queue
[_moviePlayer advanceToNextItem];
// if this is the last item in the queue, add the videos back in
if (_moviePlayer.items.count == 1)
{
// it'd be more efficient to make this a method being we're using it a second time
for (int i = 0; i < 5; i++)
{
NSString *fileName = [NSString stringWithFormat:#"intro%i", i];
NSString *path = [[NSBundle mainBundle] pathForResource:fileName ofType:#"mov"];
NSURL *movieUrl = [NSURL fileURLWithPath:path];
AVPlayerItem *item = [AVPlayerItem playerItemWithURL:movieUrl];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:item];
// the difference from last time, we're adding the new item after the last item in our player to maintain the order
[_moviePlayer insertItem:item afterItem:[[_moviePlayer items] lastObject]];
}
}
}
In Swift 4, you can use something like the following:
NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil, queue: nil) {
notification in
print("A PlayerItem just finished playing !")
currentVideo += 1
if(currentVideo >= videoPaths.count) {
currentVideo = 0
}
let url = URL(fileURLWithPath:videoPaths[currentVideo])
let playerItem = AVPlayerItem.init(url: url)
player.insert(playerItem, after: nil)
}
From what I understand the AVQueuePlayer will remove the last item played, so you need to keep adding the video just played to the queue, otherwise it will actually run out of videos to play! You can't just .seek to 0 and .play() again, that doesn't work.
working on iOS 14 as of March 2021
Just wanted to post my solution as a new coder this took me a while to figure out.
my solution loops a avqueuplayer inside of a UIView without using AVLooper. The idea came from raywenderlich. You can ignore the UIView wrapper ect. The idea is to use the NSKeyValeObservation and not the notification center as everyone else suggests.
class QueuePlayerUIView: UIView {
private var playerLayer = AVPlayerLayer()
private var playerLooper: AVPlayerLooper?
#objc let queuePlayer = AVQueuePlayer()
var token: NSKeyValueObservation?
var clips = [URL]()
func addAllVideosToPlayer() {
for video in clips {
let asset = AVURLAsset(url: video)
let item = AVPlayerItem(asset: asset)
queuePlayer.insert(item, after: queuePlayer.items().last)
}
}
init(videosArr: [URL]) {
super.init(frame: .zero)
self.clips = videosArr
addAllVideosToPlayer()
playerLayer.player = queuePlayer
playerLayer.videoGravity = .resizeAspectFill
layer.addSublayer(playerLayer)
queuePlayer.play()
token = queuePlayer.observe(\.currentItem) { [weak self] player, _ in
if player.items().count == 1 {
self?.addAllVideosToPlayer()
}
}
}
override func layoutSubviews() {
super.layoutSubviews()
playerLayer.frame = bounds
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Getting video from ALAsset

Using the new asset library framework available in iOS 4 i see that I can get the url for a given video using the UIImagePickerControllerReferenceURL. The url returned is in the following format:
assets-library://asset/asset.M4V?id=1000000004&ext=M4V
I am trying to upload this video to a website so as a quick proof of concept I am trying the following
NSData *data = [NSData dataWithContentsOfURL:videourl];
[data writeToFile:tmpfile atomically:NO];
Data is never initialized in this case. Has anyone managed to access the url directly via the new assets library? Thanks for your help.
I use the following category on ALAsset:
static const NSUInteger BufferSize = 1024*1024;
#implementation ALAsset (Export)
- (BOOL) exportDataToURL: (NSURL*) fileURL error: (NSError**) error
{
[[NSFileManager defaultManager] createFileAtPath:[fileURL path] contents:nil attributes:nil];
NSFileHandle *handle = [NSFileHandle fileHandleForWritingToURL:fileURL error:error];
if (!handle) {
return NO;
}
ALAssetRepresentation *rep = [self defaultRepresentation];
uint8_t *buffer = calloc(BufferSize, sizeof(*buffer));
NSUInteger offset = 0, bytesRead = 0;
do {
#try {
bytesRead = [rep getBytes:buffer fromOffset:offset length:BufferSize error:error];
[handle writeData:[NSData dataWithBytesNoCopy:buffer length:bytesRead freeWhenDone:NO]];
offset += bytesRead;
} #catch (NSException *exception) {
free(buffer);
return NO;
}
} while (bytesRead > 0);
free(buffer);
return YES;
}
#end
This is not the best way to do this. I am answering this question in case another SO user comes across the same issue.
Basically my need was to be able to spool the video file to a tmp file so I can upload it to a website using ASIHTTPFormDataRequest. There is probably a way of streaming from the asset url to the ASIHTTPFormDataRequest upload but I could not figure it out. Instead I wrote the following function to drop the file to a tmp file to add to ASIHTTPFormDataRequest.
+(NSString*) videoAssetURLToTempFile:(NSURL*)url
{
NSString * surl = [url absoluteString];
NSString * ext = [surl substringFromIndex:[surl rangeOfString:#"ext="].location + 4];
NSTimeInterval ti = [[NSDate date]timeIntervalSinceReferenceDate];
NSString * filename = [NSString stringWithFormat: #"%f.%#",ti,ext];
NSString * tmpfile = [NSTemporaryDirectory() stringByAppendingPathComponent:filename];
ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset)
{
ALAssetRepresentation * rep = [myasset defaultRepresentation];
NSUInteger size = [rep size];
const int bufferSize = 8192;
NSLog(#"Writing to %#",tmpfile);
FILE* f = fopen([tmpfile cStringUsingEncoding:1], "wb+");
if (f == NULL) {
NSLog(#"Can not create tmp file.");
return;
}
Byte * buffer = (Byte*)malloc(bufferSize);
int read = 0, offset = 0, written = 0;
NSError* err;
if (size != 0) {
do {
read = [rep getBytes:buffer
fromOffset:offset
length:bufferSize
error:&err];
written = fwrite(buffer, sizeof(char), read, f);
offset += read;
} while (read != 0);
}
fclose(f);
};
ALAssetsLibraryAccessFailureBlock failureblock = ^(NSError *myerror)
{
NSLog(#"Can not get asset - %#",[myerror localizedDescription]);
};
if(url)
{
ALAssetsLibrary* assetslibrary = [[[ALAssetsLibrary alloc] init] autorelease];
[assetslibrary assetForURL:url
resultBlock:resultblock
failureBlock:failureblock];
}
return tmpfile;
}
Here is a clean swift solution to get videos as NSData.
It uses the Photos framework as ALAssetLibrary is deprecated as of iOS9:
IMPORTANT
The Assets Library framework is deprecated as of iOS 9.0. Instead, use the Photos framework instead, which in iOS 8.0 and later provides more features and better performance for working with a user’s photo library. For more information, see Photos Framework Reference.
import Photos
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
self.dismissViewControllerAnimated(true, completion: nil)
if let referenceURL = info[UIImagePickerControllerReferenceURL] as? NSURL {
let fetchResult = PHAsset.fetchAssetsWithALAssetURLs([referenceURL], options: nil)
if let phAsset = fetchResult.firstObject as? PHAsset {
PHImageManager.defaultManager().requestAVAssetForVideo(phAsset, options: PHVideoRequestOptions(), resultHandler: { (asset, audioMix, info) -> Void in
if let asset = asset as? AVURLAsset {
let videoData = NSData(contentsOfURL: asset.URL)
// optionally, write the video to the temp directory
let videoPath = NSTemporaryDirectory() + "tmpMovie.MOV"
let videoURL = NSURL(fileURLWithPath: videoPath)
let writeResult = videoData?.writeToURL(videoURL, atomically: true)
if let writeResult = writeResult where writeResult {
print("success")
}
else {
print("failure")
}
}
})
}
}
}
There you go...
AVAssetExportSession* m_session=nil;
-(void)export:(ALAsset*)asset withHandler:(void (^)(NSURL* url, NSError* error))handler
{
ALAssetRepresentation* representation=asset.defaultRepresentation;
m_session=[AVAssetExportSession exportSessionWithAsset:[AVURLAsset URLAssetWithURL:representation.url options:nil] presetName:AVAssetExportPresetPassthrough];
m_session.outputFileType=AVFileTypeQuickTimeMovie;
m_session.outputURL=[NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:#"%f.mov",[NSDate timeIntervalSinceReferenceDate]]]];
[m_session exportAsynchronouslyWithCompletionHandler:^
{
if (m_session.status!=AVAssetExportSessionStatusCompleted)
{
NSError* error=m_session.error;
m_session=nil;
handler(nil,error);
return;
}
NSURL* url=m_session.outputURL;
m_session=nil;
handler(url,nil);
}];
}
You can use a different preset key if you wish to re-encode the movie (AVAssetExportPresetMediumQuality for example)
Here is the Objective C solution of Alonzo answer, Using photos framework
-(NSURL*)createVideoCopyFromReferenceUrl:(NSURL*)inputUrlFromVideoPicker{
NSURL __block *videoURL;
PHFetchResult *phAssetFetchResult = [PHAsset fetchAssetsWithALAssetURLs:#[inputUrlFromVideoPicker ] options:nil];
PHAsset *phAsset = [phAssetFetchResult firstObject];
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[[PHImageManager defaultManager] requestAVAssetForVideo:phAsset options:nil resultHandler:^(AVAsset *asset, AVAudioMix *audioMix, NSDictionary *info) {
if ([asset isKindOfClass:[AVURLAsset class]]) {
NSURL *url = [(AVURLAsset *)asset URL];
NSLog(#"Final URL %#",url);
NSData *videoData = [NSData dataWithContentsOfURL:url];
// optionally, write the video to the temp directory
NSString *videoPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:#"%f.mp4",[NSDate timeIntervalSinceReferenceDate]]];
videoURL = [NSURL fileURLWithPath:videoPath];
BOOL writeResult = [videoData writeToURL:videoURL atomically:true];
if(writeResult) {
NSLog(#"video success");
}
else {
NSLog(#"video failure");
}
dispatch_group_leave(group);
// use URL to get file content
}
}];
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
return videoURL;
}
this from Zoul's Answer
thanks
Similar Code in Xamarin C#
Xamarin C# Equivalent
IntPtr buffer = CFAllocator.Malloc.Allocate(representation.Size);
NSError error;
nuint buffered = representation.GetBytes(buffer, Convert.ToInt64(0.0),Convert.ToUInt32(representation.Size),out error);
NSData sourceData = NSData.FromBytesNoCopy(buffer,buffered,true);
NSFileManager fileManager = NSFileManager.DefaultManager;
NSFileAttributes attr = NSFileAttributes.FromDictionary(NSDictionary.FromFile(outputPath));
fileManager.CreateFile(outputPath, sourceData,attr);

How do I get the filesystem path for a resource on iPhone?

On the iPhone I need to get the path for the resource. OK, done that, but when it comes to CFURLCreateFromFileSystemRepresentation thing, I just don't know how to solve this. Why does this error occur? Any solution or workaround would be highly appreciated. Thank you in advance.
I have taken a look at the following examples in order to play audio using AudioQueue on the iPhone:
SpeakHere, AudioQueueTools (from the SimpleSDK directory) and AudioQueueTest. I tried do this and that, trying to put the puzzles together. Right now, I am stuck at this. The program crashed because of the exception thrown from the sndFile above.
I am using AVAudioPlayer to play every sound on my iPhone games. On the real iPhone device, it turned out to be very laggy when a sound is to be played, so I decided I need to use AudioQueue.
- (id) initWithFile: (NSString*) argv{
if (self = [super init]){
NSString *soundFilePath = [[NSBundle mainBundle]
pathForResource:argv
ofType:#"mp3"];
int len = [soundFilePath length];
char* fpath = new char[len];
//this is for changing NSString into char* to match
//CFURLCreateFromFileSystemRepresentation function's requirement.
for (int i = 0; i < [soundFilePath length]; i++){
fpath[i] = [soundFilePath characterAtIndex:i];
}
CFURLRef sndFile = CFURLCreateFromFileSystemRepresentation
(NULL, (const UInt8 *)fpath, strlen(fpath), false);
if (!sndFile) {
NSLog(#"sndFile error");
XThrowIfError (!sndFile, "can't parse file path");
}
}
Why do you need a CFURL?
If you have a method elsewhere that requires a CFURL, you can simply use an NSURL thanks to toll-free bridging. So to create the NSURL, you'd just do:
NSString * soundFilePath = [[NSBundle mainBundle]
pathForResource:argv
ofType:#"mp3"];
NSURL *soundURL = [NSURL fileURLWithPath:soundFilePath];
In general, if you find yourself using CF objects you are probably doing something wrong.
I'm not sure if this will get rid of your exception, but there is a simpler way to convert an NSString to an array of char. Here is how I would write this method:
- (id) initWithFile:(NSString*) argv
{
if ((self = [super init]) == nil) { return nil; }
NSString * soundFilePath = [[NSBundle mainBundle]
pathForResource:argv
ofType:#"mp3"];
CFURLRef sndFile = CFURLCreateFromFileSystemRepresentation
(NULL, [soundFilePath UTF8String],
[soundFilePath length], NO);
if (!sndFile) { NSLog(#"sndFile error"); }
XThrowIfError (!sndFile, "can't parse file path");
...
}
Or, since CFURL is "toll-free bridged" with NSURL, you can simply do:
- (id) initWithFile:(NSString*) argv
{
if ((self = [super init]) == nil) { return nil; }
NSString * soundFilePath = [[NSBundle mainBundle]
pathForResource:argv
ofType:#"mp3"];
NSURL * sndFile = [NSURL URLWithString:[soundFilePath
stringByAddingPercentEscapesUsingEncoding:
NSUTF8StringEncoding]];
if (!sndFile) { NSLog(#"sndFile error"); }
XThrowIfError (!sndFile, "can't parse file path");
...
}