0

I'm writing an app which plays an mp3 file.

In Android this is straight forward:

  1. Use Xamarin.Essentials.FilePicker to pick the mp3 filepath
  2. Use Android.Media.MediaPlayer to play the mp3 filepath

I tried a similar approach for iOS:

  1. Use Xamarin.Essentials.FilePicker to pick the mp3 filepath (either from icloud or on phone)
  2. Use AVAudioEngine to play the mp3 filepath

This works in the simulator but throws an exception when trying to create an AVAudioFile. (LibVlcSharp also fails when trying to load the filepath).

NSError err;
AVAudioFile file = new AVAudioFile(new NSUrl(audioUri, false), out err);
//err is null after this call throws an exception

Exception: "Could not initialize an instance of the type 'AVFoundation.AVAudioFile': the native 'initForReading:error:' method returned nil.\nIt is possible to ignore this condition by setting ObjCRuntime.Class.ThrowOnInitFailure to false."

(Setting ThrowOnInitFailure = false doesn't help as file is still null).

I suspect it's a permissions issue, so I tried using the MPMediaPickerController:

MPMediaPickerController mp = new MPMediaPickerController();
mp.AllowsPickingMultipleItems = false;
mp.ShowsCloudItems = true;
  UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(mp, true, null);

with MPMediaLibraryAuthorizationStatus set to Authorized and the following set in info.plist:

<key>NSAppleMusicUsageDescription</key>
<string>Select a music file</string>

This code does nothing whatsoever.

How can I get this or the file picker method to work?

UPDATE

I've managed to get the MediaPicker working. Will update on that soon.

My next problem is I get an NSUrl from the MediaPicker. If I pass this directly into the AVAudioEngine, it plays fine.

But if I save the NSUrl to a string for serialization purposes, and then try to recreate it with the following code, the same exception above occurs when trying to create AVAudioFile.

//url1 works fine
NSUrl url1 = mediaItemCollection.Items[0].AssetURL;
AVAudioFile file = new AVAudioFile(url1, out err);
        
//url2 throws the same exception above
string str = mediaItemCollection.Items[0].AssetURL.AbsoluteString;
NSUrl url2 = new NSUrl(str, false) 
AVAudioFile file = new AVAudioFile(url2, out err);

Any ideas?

jho
  • 199
  • 1
  • 8

2 Answers2

0

You could call NSUrl.StartAccessingSecurityScopedResource Method and check the return value:

NSUrl url = NSUrl.FromString(audioUri);
var securityEnabled = url.StartAccessingSecurityScopedResource();

For more info, you could refer to startAccessingSecurityScopedResource

============= Origin answer======

You may try using AVPlayer.

AVPlayerItem aVPlayerItem = new AVPlayerItem(new NSUrl(audioUri));
AVPlayer aVPlayer = new AVPlayer(aVPlayerItem);
aVPlayer.Play();

also add this in info.plist if you load from a http source

<key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
    </dict>

Hope it works.

Liqun Shen-MSFT
  • 3,490
  • 2
  • 3
  • 11
  • Thanks for your reply Liqun. I've just tried your code but it throws an exception when creating the NSUrl: Could not initialize an instance of the type 'Foundation.NSUrl': the native 'initWithString:' method returned nil. Also at the end of the day, I still need the AVEngine because I need to be able to PitchShift the audio which AVPlayer can't do. – jho Jun 19 '23 at 12:43
  • @jho You may try call ```NSUrl.StartAccessingSecurityScopedResource``` method. I have updated my answer. – Liqun Shen-MSFT Jun 19 '23 at 15:09
  • I tried your new code, but `securityEnable == false` and same error occurred creating my `AVAudioFile`. Please see **Updates** to my post above. – jho Jun 19 '23 at 17:11
0

Finally achieved playback. I will document below the major obstacles I encountered and how I resolved them, in case anyone finds this useful.

Displaying MPMediaPickerController

The code above didn't work because I was trying to launch it from a popup window and thus needed to find the popup's corresponding ViewController instead of the RootViewController I was using above.

This post had the solution: How to find current UIViewController in Xamarin

Serializing a NSUrl

In order to Serialize an MPMediaItem you can't simply use the Url string. You need to use:

MPMediaItem.PersistentID which is a ulong.

To recreate a NSUrl from the ulong, you need to:

  1. Query the media library again for the MPMediaItem with that PersistentID using MPMediaQuery and MPMediaPropertyPredicate
  2. Use the query resultant MPMediaItem's AssetURL property

Playing audio but no sound comes solution

This one drove me crazy but turns out one of the phone's volume settings was preventing the sound emission.

This post and the following code solved the problem: Swift: Playing audio using AVPlayer - Audio is not playing, cannot hear audio

AVAudioSession.SharedInstance().SetCategory(AVAudioSession.CategoryPlayback);
AVAudioSession.SharedInstance().SetActive(true);

Thanks everyone for your help on this. It was much appreciated. Happy coding!

jho
  • 199
  • 1
  • 8
  • Glad you solved it! You could accept your answer, which may help others with similar question. – Liqun Shen-MSFT Jun 21 '23 at 05:08
  • Can't accept my own post unfortunately, but thanks for your help anyway. It kept pushing me along until I found the answer :) – jho Jun 23 '23 at 07:27