Jump to content
Sign in to follow this  
Rollo62

iOSapi_AVFoundation AVAudioSession extension doesn't work

Recommended Posts

Hi there,

 

I was trying to extend the iOSapi_AVFoundation.pas module, by adding the following function to the AVAudioSession

 

    function setCategoryWithOptionsError( category: NSString;
                                          withOptions : AVAudioSessionCategoryOptions;
                                          error: NSError): Boolean; cdecl;

Unfortunately its crashing with an object is nil error, what do I miss here ?

image.png.f9dbb8c57b6c89d575d7a117af2970b0.png

 

I make a local copy of the unit, and the following additions:

uses
...

const
  AVAudioSessionCategoryOptionDuckOthers = 2; //S4: add 14.05.20
  
...

type
  AVAudioSessionCategoryOptions = NSUInteger; //S4: add 14.05.20
  
  AVAudioSessionClass = interface(NSObjectClass)
    ['{B24932F9-3C98-44E4-A4F6-0CB58AF7DE8A}']  //S4: 14.05.20  new GUID
    {class} function sharedInstance: Pointer; cdecl;
  end;
  AVAudioSession = interface(NSObject)
    ['{0B02D5EC-ED09-421A-84BE-6CEE08420E14}']  //S4: 14.05.20  new GUID

...

    //S4: Addition 14.05.20, the new function I need
    //[MethodName('setCategory:withOptions:error:')]
    function setCategoryWithOptionsError( category: NSString;
                                          withOptions : AVAudioSessionCategoryOptions;
                                          error: NSError): Boolean; cdecl;
                                          
...

end.


 

This is derived from the function in the Apple reference

https://developer.apple.com/documentation/avfoundation/avaudiosession/1616442-setcategory

 

Could be looking in Objective-C like this

AVAudioSessionCategoryOptions AVAudioSessionCategoryOptionsNone = 0;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionsNone error:nil];

 

 

I want to call it like this

    LAVAudioSession := TAVAudioSession.Wrap( TAVAudioSession.OCClass.sharedInstance );

    //<-- This is original method, it still can be called without exception
    LRes := LAVAudioSession.setCategory( AVAudioSessionCategoryPlayback,
                                         LError);

    //<-- This is the new method, crash with exception
    LRes := LAVAudioSession.setCategoryWithOptionsError(
                                         AVAudioSessionCategoryPlayback,
                                         AVAudioSessionCategoryOptionDuckOthers,
                                         LError);

 

 

Do I have a stupid typo somewhere ?

Can the two modules not coexist, because the old one is linked somewhere else ?

Shall I use different GUID for the interfaces, or keep the same, to allow static links to find them ?

Is the new method not yet in the Rx10.3.3  libraries, but I updated to latest SDK 13.4.1, they should be in there and FMX able to resolve it ?

Does the module need to be initialized somehow ?

Is there something missing what is needed for correct linkage ?

 

Maybe its too late already, or I'm to blind to see.

 

 

 

 

 

 

 

 

Share this post


Link to post
4 hours ago, Rollo62 said:

function setCategoryWithOptionsError( category: NSString; withOptions : AVAudioSessionCategoryOptions; error: NSError😞 Boolean; cdecl;

 

It should be:

function setCategoryWithOptionsError(category: NSString; withOptions: AVAudioSessionCategoryOptions; error: PPointer): Boolean; cdecl;

But you don't show how you're calling it, either. A possible example:

var
  LErrorPtr: Pointer;
  LError: NSError;
  
 ...
 
   setCategoryWithOptionsError(CocoaNSStringConst(libAVFoundation, 'AVAudioSessionCategoryPlayAndRecord'), 0, @LErrorPtr);
   if LErrorPtr <> nil then
   begin
     LError := TNSError.Wrap(LErrorPtr);
     // Deal with LError here
   end;

 

4 hours ago, Rollo62 said:

Shall I use different GUID for the interfaces,

You should.

 

4 hours ago, Rollo62 said:

Is the new method not yet in the Rx10.3.3  libraries,

No. I suggest filing a QP report.

  • Like 1

Share this post


Link to post

@Dave Nottage

Thanks for the correction, I will check this out.

 

What I really dont understand why this should be different.
There is a prototype in the iOSapi_Foundation.pas file already, the setCategory

function setCategory(theCategory: NSString; error: NSError): Boolean; cdecl;

It uses the error: NSError type

 

I simply tried to copy that, and extent to

function setCategoryWithOptionsError( category: NSString;
                                      withOptions : AVAudioSessionCategoryOptions;
                                      error: NSError): Boolean; cdecl;

Why should it be error: PPointer, right now ?

function setCategoryWithOptionsError(category: NSString; withOptions: AVAudioSessionCategoryOptions; error: PPointer): Boolean; cdecl;

Also the FmxExpress header conversions from @Eli M. shows it with special NSError type.

 

Edit:

NSError is defined in iOSap_Foundation as interface

PPointer ist jus a pointer to pointer in System

  NSError = interface(NSObject)
    ['{8E8F832A-5F75-4F65-A18B-5A8E2F49A867}']
    function code: NSInteger; cdecl;
    function domain: NSString; cdecl;
    function helpAnchor: NSString; cdecl;
    function initWithDomain(domain: NSString; code: NSInteger; userInfo: NSDictionary): Pointer; cdecl;
    function localizedDescription: NSString; cdecl;
    function localizedFailureReason: NSString; cdecl;
    function localizedRecoveryOptions: NSArray; cdecl;
    function localizedRecoverySuggestion: NSString; cdecl;
    function recoveryAttempter: Pointer; cdecl;
    function userInfo: NSDictionary; cdecl;
  end;
...
  PPointer      = ^Pointer;     {$NODEFINE PPointer}    { defined in sysmac.h }

How can the type of "error" change in one unit, is this another Apple-Magic ?

 

And the original libraries from Apple seems to use similar types as well, here are all the variants.
Why to use NSError in one, and PPointer in the other, I don't get that ?

func setCategory(_ category: AVAudioSession.Category) throws

func setCategory(_ category: AVAudioSession.Category, 
         options: AVAudioSession.CategoryOptions = []) throws

func setCategory(_ category: AVAudioSession.Category, 
            mode: AVAudioSession.Mode, 
         options: AVAudioSession.CategoryOptions = []) throws

func setCategory(_ category: AVAudioSession.Category, 
            mode: AVAudioSession.Mode, 
         options: AVAudioSession.CategoryOptions = []) throws

 

I call this as shown above, those are predefined function, to get the necessary string  key constant.

which is defined
in iOSapi_Foundation

    function AVAudioSessionCategoryPlayback: NSString;


in caller unit

var

  LError          : NSError;

  LRes := LAVAudioSession.setCategory( AVAudioSessionCategoryPlayback, LError);

 

 

 

Quote
13 hours ago, Rollo62 said:

Shall I use different GUID for the interfaces, 

You should.

I meant when I import make a local copy of the unit, which has hundrets or interfaces keeping untouched.
Will it be enough to use different GUID only for those interfaces that have changed, not the ones that keep original ?
Still then would be 2 references 8old unit, new unit) of units which may have same interface GUID.

Do I have to change all the GUIDS in that local copy of the unit (that would be a little messy) ?

 

Quote
13 hours ago, Rollo62 said:

Is the new method not yet in the Rx10.3.3  libraries,

No. I suggest filing a QP report.

You mean feature request for the iOSapi_Foundation, right ?

Its clear that the interface is missing in iOSapi_Foundation, but I was not sure if there is anything else might missing in the static libraries.
Since I assume those are copied and get actually from the current system, this should always up to date.

My understanding of the iOS-Delphi bridge is that SDK from current system is bounded via the interfaces to Delphi classes,
or is there anything hidden inbetween that I haven't seen yet ?

 

 

 

Edited by Rollo62

Share this post


Link to post
1 hour ago, Rollo62 said:

There is a prototype in the iOSapi_Foundation.pas file already, the setCategory

I think you mean in iOSapi.AVFoundation.pas. You're working under the assumption that the declaration is correct - it is not.

 

1 hour ago, Rollo62 said:

Why should it be error: PPointer, right now ?

Because the corresponding declaration in Objective-C is:

- (BOOL)setCategory:(AVAudioSessionCategory)category error:(NSError **)outError

Note the double asterisk. Because the Objective-C bridge is presently unable to handle wrapping this, a PPointer can be used in the way I described in my reply. As an example, see the code for the DownloadableContentPath method in FMX.InAppPurchase.iOS, which uses the setResourceValue method of NSURL, which is declared correctly.

 

1 hour ago, Rollo62 said:

You mean feature request for the iOSapi_Foundation, right ?

It's a QP report either way, however I'm referring to the missing declaration for the setCategoryWithOptionsError method of AVAudioSession (and any others methods that might be needed) in iOSapi.AVFoundation.

1 hour ago, Rollo62 said:

Since I assume those are copied and get actually from the current system,

No, that's not automatic.

1 hour ago, Rollo62 said:

this should always up to date

It would be nice if it were, however translating SDK framework headers is not trivial, and as you've observed declaration errors can happen, and/or the Objective-C bridge needs to be modified to handle situations where needed. Up until 10.3.2 (or somewhere close to that), the bridge could not even handle Objective-C blocks on macOS (it could on iOS).

Share this post


Link to post
Quote

You're working under the assumption that the declaration is correct - it is not.

A yes, great.

So I better check the c headers from now on, instead of Apple Docs.

Every time I see a NSError now I will have to replace it bei PPointer, what a mess :classic_huh:


Then there were many wrong entries in the unit,
I only wonder why this seems to work with setCategory, at least I get no exceptions.

 

Unfortunately it still doesn't run without exception, and I think I've tried all optiions, see alternatives (A) to (C) and (1) to (5),
I've tried most combinations with same result.

When I don't use local copy with different name iOSapi_AVFoundation_Ex I do get compiler errors in FMX.Media.iOS,

seems that one uses the original unit, and cannot work with my local copy.

Thats why I used a different unit name, that also is working for other iOS units before.

unit iOSapi_AVFoundation_Ex;  //<-- local copy, with extensions and changed GUID

var
    LAVAudioSession : AVAudioSession;
    LError          : NSError;
    LRes            : Boolean;

    (A) as PPointer, called by  @LPError
    (B) as PPointer, called by   LPError (although this makes not much sense)
    //LPError         : PPointer;

    (C) as Pointer,  called by  @LPError
    LPError         : Pointer;

begin
    LAVAudioSession := TAVAudioSession.Wrap( TAVAudioSession.OCClass.sharedInstance );

    // (1) this works as far as I can say, at least never see exceptions I can say
    LRes := LAVAudioSession.setCategory( AVAudioSessionCategoryPlayback,
                                         LError);

    // (2) first approach, throws exception
    LRes := LAVAudioSession.setCategoryWithOptionsError(
                                         AVAudioSessionCategoryPlayback,
                                         AVAudioSessionCategoryOptionDuckOthers,
                                         LError);

    // (3) Dave's proposal
    // Because the corresponding declaration in Objective-C is:
    // - (BOOL)setCategory:(AVAudioSessionCategory)category error:(NSError **)outError
    //PPointer      = ^Pointer;     {$NODEFINE PPointer}    { defined in sysmac.h }
    //NSError = interface;
    //

    (A), (B) set to nil before
    LPError := nil;

    //<-- this always shows same crash, no matter what (A),(B),(C),  (2)-(5)
    LRes := LAVAudioSession.setCategoryWithOptionsError(
            // (4a) this alternative both version should be identical -> get from string
            //CocoaNSStringConst(libAVFoundation, 'AVAudioSessionCategoryPlayback'),
            // (4b) this alternative get from dedicated function, doing same as above
            //AVAudioSessionCategoryPlayback,
            // (4c) this alternative from Dave's original proposal, using different mode
            CocoaNSStringConst(libAVFoundation, 'AVAudioSessionCategoryPlayAndRecord'),
            // (5a) this alternative is a simple integer constant 0x2, defined as const = 2 
            AVAudioSessionCategoryOptionDuckOthers,  // 2
            // (5b) this alternative also crashes same 
            //2,
            // (5c) this alternative from Dave's original proposal, using different option
            0,
           @LPError);
           
        if LPError <> nil then
        begin
            LError := TNSError.Wrap(LPError);
        end;
        if not LRes or (LError <> nil) then
        begin // Something wrong

        end;



 

Edited by Rollo62

Share this post


Link to post
Quote

declaration errors can happen, and/or the Objective-C bridge needs to be modified to handle situations where needed. Up until 10.3.2 (or somewhere close to that), the bridge could not even handle Objective-C blocks on macOS (it could on iOS).

@Dave Nottage

Or did I misunderstand your post, and you say this is currently under 10.3.3 NOT usable, even with the proposals you gave,

because Objective-C bridge cannot handle it yet ?

 

That would be the worst case, I need that urgently, my customers already ask why can't we do what other apps can do.

I monitor the changes in all Delphi iOSApi and AndroidApi units since the last 5 or 6 versions,
so far never changed anything since years, only copyright year is eagerly updated.

My hope is that all the available system interfaces might get linked to FMX units soon.

 

Is there maybe a helpful unit from the FPC community, at least this didn't jump in my eye yet ?

Any solution welcome, to configure background and foreground audio.

Edited by Rollo62

Share this post


Link to post
13 hours ago, Rollo62 said:

Every time I see a NSError now I will have to replace it bei PPointer, what a mess

Every time you see a NSError** you'll need to replace it with PPointer. Also, if you see:

(NSError * _Nullable * _Nullable)

(Which amounts to the same thing)

12 hours ago, Rollo62 said:

Or did I misunderstand your post, and you say this is currently under 10.3.3 NOT usable, even with the proposals you gave,

I was just pointing out the incorrect declaration; I have not looked at any other aspect as yet. I'll take a look at your example today and see what the problem might be.

Share this post


Link to post
2 hours ago, Dave Nottage said:

I'll take a look at your example today and see what the problem might be.

I should have realised from the beginning - it's a method resolution problem. The Objective-C method name is actually setCategory, not setCategoryWithOptionsError, so it needs a MethodName attribute. This executes without error for me:

 

uses
  Macapi.ObjectiveC, Macapi.Helpers,
  iOSapi.Foundation, iOSapi.AVFoundation, iOSapi.CocoaTypes;

const
  AVAudioSessionCategoryOptionNone = 0;
  AVAudioSessionCategoryOptionMixWithOthers = 1;
  AVAudioSessionCategoryOptionDuckOthers = 2;
  AVAudioSessionCategoryOptionAllowBluetooth = 4;
  AVAudioSessionCategoryOptionDefaultToSpeaker = 8;
  AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers = 17;
  AVAudioSessionCategoryOptionAllowBluetoothA2DP = 32;
  AVAudioSessionCategoryOptionAllowAirPlay = 64;

type
  AVAudioSessionCategory = NSString;
  AVAudioSessionCategoryOptions = NSInteger;

  AVAudioSessionClass = interface(NSObjectClass)
    ['{F8B0F7A3-1805-4739-B827-3AE7FFCD20F0}']
    {class} function sharedInstance: Pointer; cdecl;
  end;
  AVAudioSession = interface(NSObject)
    ['{C6EAD2A6-DE66-4A80-B019-8564FF6927AF}']
    function category: NSString; cdecl;
    function currentHardwareInputNumberOfChannels: NSInteger; cdecl;
    function currentHardwareOutputNumberOfChannels: NSInteger; cdecl;
    function currentHardwareSampleRate: double; cdecl;
    function delegate: Pointer; cdecl;
    function inputIsAvailable: Boolean; cdecl;
    function mode: NSString; cdecl;
    function preferredHardwareSampleRate: double; cdecl;
    function preferredIOBufferDuration: NSTimeInterval; cdecl;
    function setActive(beActive: Boolean; error: PPointer): Boolean; cdecl; overload;
    function setActive(beActive: Boolean; withFlags: NSInteger; error: PPointer): Boolean; cdecl; overload;
    function setCategory(theCategory: NSString; error: PPointer): Boolean; cdecl;
    [MethodName('setCategory:withOptions:error:')]
    function setCategoryWithOptionsError(category: AVAudioSessionCategory; withOptions: AVAudioSessionCategoryOptions; error: PPointer): Boolean; cdecl;
    procedure setDelegate(delegate: Pointer); cdecl;
    function setMode(theMode: NSString; error: PPointer): Boolean; cdecl;
    function setPreferredHardwareSampleRate(sampleRate: double; error: PPointer): Boolean; cdecl;
    function setPreferredIOBufferDuration(duration: NSTimeInterval; error: PPointer): Boolean; cdecl;
  end;
  TAVAudioSession = class(TOCGenericImport<AVAudioSessionClass, AVAudioSession>)  end;

procedure TForm1.Button1Click(Sender: TObject);
var
  LSession: AVAudioSession;
  LErrorPtr: Pointer;
begin
  LErrorPtr := nil;
  LSession := TAVAudioSession.Wrap(TAVAudioSession.OCClass.sharedInstance);
  if LSession.setCategoryWithOptionsError(AVAudioSessionCategoryPlayback, AVAudioSessionCategoryOptionNone, @LErrorPtr) then
    Label1.Text := 'Set category OK'
  else if LErrorPtr <> nil then
    Label1.Text := NSStrToStr(TNSError.Wrap(LErrorPtr).localizedDescription);
end;

 

Share this post


Link to post
11 hours ago, Dave Nottage said:

it needs a MethodName attribute. This executes without error for me:

 

A right.

I even hat that in my code in the first place, but stupid me commented that out.
So I will try again.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×