Android Code Snippet Choose audio file and play with MediaPlayer

Although the use case (see title) sounds trivial, it took some time to get the result. The search returned many hits on the topic, from which the last valid "Single Point of Truth" could be found.

The obstacles were the read permission and the path provided by the ContentChooser.

Thanks to Erel's "GetPathFromContentResult()" the path format expected by the MediaPlayer could be determined.


B4X:
' ------------------------
' Mainfest
' ------------------------
'This code will be applied to the manifest file during compilation.
'You do not need to modify it in most cases.
'See this link for for more information: https://www.b4x.com/forum/showthread.php?p=78136
AddManifestText(
<uses-sdk android:minSdkVersion="15" android:targetSdkVersion="28"/>
<supports-screens android:largeScreens="true"
    android:normalScreens="true"
    android:smallScreens="true"
    android:anyDensity="true"/>)
SetApplicationAttribute(android:icon, "@drawable/icon")
SetApplicationAttribute(android:label, "$LABEL$")
CreateResourceFromFile(Macro, Themes.DarkTheme)
'End of default text.

AddPermission(android.permission.WRITE_EXTERNAL_STORAGE)
AddPermission(android.permission.READ_EXTERNAL_STORAGE)


' ------------------------
' Main
' ------------------------
#Region  Project Attributes
    #ApplicationLabel: B4A Example
    #VersionCode: 1
    #VersionName:
    'SupportedOrientations possible values: unspecified, landscape or portrait.
    #SupportedOrientations: unspecified
    #CanInstallToExternalStorage: False
#End Region

#Region  Activity Attributes
    #FullScreen: True   
    #IncludeTitle: False
#End Region

Sub Process_Globals
    Private mp As MediaPlayer
End Sub

Sub Globals
    Private LabelDir As Label
    Private LabelFile As Label
    Private LabelFilePath As Label
    Private Action_Play As Label
    Private Action_Stop As Label
End Sub

Sub Activity_Create(FirstTime As Boolean)
    Activity.LoadLayout("Layout1")
    mp.Initialize
End Sub

Sub Activity_Resume
End Sub
Sub Activity_Pause (UserClosed As Boolean)
End Sub

Sub Button1_Click As ResumableSub
    Dim cc As ContentChooser
    cc.Initialize("cc")
    cc.Show("audio/*", "Choose audio file")
    wait for cc_Result (Success As Boolean, Dir As String, FileName As String)

    If Success Then
        LabelDir.Text  = Dir
        LabelFile.Text = FileName
        Log("#-  x51, LabelDir.Text  = " & LabelDir.Text)   ' ContentDir
        Log("#-  x52, LabelFile.Text = " & LabelFile.Text)  ' content://com.android.providers.media.documents/document/audio%3A128

        Private rp As RuntimePermissions
        rp.CheckAndRequest(rp.PERMISSION_READ_EXTERNAL_STORAGE)
        Wait For Activity_PermissionResult (permission As String, Result As Boolean)
        If Result = False Then
            Return Null
        End If
        
        Dim FilePath As String = GetPathFromContentResult(LabelFile.Text, "audio")
        LabelFilePath.Text = FilePath ' /storage/emulated/0/Music/Gong - Time Is The Key/01-Ard Na Greine.mp3
        
        mp.Load("", FilePath)
        
    Else
        LabelDir.Text = "ContentChoose failed"
        LabelFile.Text = ""
        LabelFilePath.Text = ""
    End If
    
    Action_Play.Visible = Success
    Return Null
End Sub

Sub Action_Play_Click
    mp.Play
    Action_Stop.Visible = True
End Sub
Sub Action_Stop_Click
    mp.Stop
    Action_Stop.Visible = False
End Sub

Sub GetPathFromContentResult(UriString As String, MediaType As String) As String
    ' Erel --> https://www.b4x.com/android/forum/threads/get-the-path-to-media-files-returned-from-contentchooser.39313/
    If UriString.StartsWith("/") Then Return UriString 'If the user used a file manager to choose the image
    Dim Cursor1 As Cursor
    Dim Uri1 As Uri
    Dim Proj() As String = Array As String("_data")
    Dim cr As ContentResolver
    cr.Initialize("")
    If UriString.StartsWith("content://com.android.providers.media.documents") Then
        Dim i As Int = UriString.IndexOf("%3A")
        Dim id As String = UriString.SubString(i + 3)

        Uri1.Parse($"content://media/external/${MediaType}/media"$) ' MediaType: images, audio

        Cursor1 = cr.Query(Uri1, Proj, "_id = ?", Array As String(id), "")
    Else
        Uri1.Parse(UriString)
        Cursor1 = cr.Query(Uri1, Proj, "", Null, "")
    End If
    Cursor1.Position = 0
    Dim res As String
    res = Cursor1.GetString("_data")
    Cursor1.Close
    Return res
End Sub

The question remains whether it is possible to use the content path
content://com.android.providers.media.documents/document/audio%3A124
directly.
 

Attachments

  • 2019-11-10_07-14-23.jpg
    2019-11-10_07-14-23.jpg
    21.4 KB · Views: 536
  • AudioChooseAndPlay.zip
    10.4 KB · Views: 508

Erel

B4X founder
Staff member
Licensed User
Longtime User
As you are you using GetPathFromContentResult it is worth adding the bold warning:

In most cases it is a mistake to use this sub. Especially in newer versions of Android. You shouldn't assume that the resource returned from ContentChooser comes from the file system and if it is, you most probably won't have permissions to directly access it.

The code above and the permissions are not needed and shouldn't be used.
You just need to pass Dir and FileName to MediaPlayer.
B4X:
 wait for cc_Result (Success As Boolean, Dir As String, FileName As String)
 If Success Then
  mp.Load(Dir, FileName)
 

fredo

Well-Known Member
Licensed User
Longtime User
mp.Load(Dir, FileName)
Thanks, it works in the test project.

But the following error message appears in the Productive app:

B4X:
' Manifest part: <uses-sdk android:minSdkVersion="15" android:targetSdkVersion="28"/>

#-  x2381, LabelX.tag    = content://com.android.providers.media.documents/document/audio%3A128

main_musitemaction_play_click (java line: 4742)
java.lang.SecurityException: Permission Denial: opening provider com.android.providers.media.MediaDocumentsProvider from ProcessRecord{311b6e3 9146:myapp/u0a187} (pid=9146, uid=10187) requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs
    at android.os.Parcel.createException(Parcel.java:1950)
    at android.os.Parcel.readException(Parcel.java:1918)
    at android.os.Parcel.readException(Parcel.java:1868)
    at android.app.IActivityManager$Stub$Proxy.getContentProvider(IActivityManager.java:4115)
    at android.app.ActivityThread.acquireProvider(ActivityThread.java:6223)
    at android.app.ContextImpl$ApplicationContentResolver.acquireUnstableProvider(ContextImpl.java:2607)
    at android.content.ContentResolver.acquireUnstableProvider(ContentResolver.java:1828)
    at android.content.ContentResolver.openTypedAssetFileDescriptor(ContentResolver.java:1442)
    at android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:1295)
    at android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:1218)
    at anywheresoftware.b4a.objects.MediaPlayerWrapper.loadAfterReset(MediaPlayerWrapper.java:91)
    at anywheresoftware.b4a.objects.MediaPlayerWrapper.Load(MediaPlayerWrapper.java:66)
    at myapp.main._musitemaction_play_click(main.java:4742)
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:196)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:180)
    at anywheresoftware.b4a.BA.raiseEvent(BA.java:176)
    at anywheresoftware.b4a.objects.ViewWrapper$1.onClick(ViewWrapper.java:80)
    at android.view.View.performClick(View.java:6600)
    at android.view.View.performClickInternal(View.java:6577)
    at android.view.View.access$3100(View.java:781)
    at android.view.View$PerformClick.run(View.java:25917)
    at android.os.Handler.handleCallback(Handler.java:873)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:220)
    at android.app.ActivityThread.main(ActivityThread.java:6929)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:870)
Caused by: android.os.RemoteException: Remote stack trace:
    at com.android.server.am.ActivityManagerService.getContentProviderImpl(ActivityManagerService.java:12853)
    at com.android.server.am.ActivityManagerService.getContentProvider(ActivityManagerService.java:13154)
    at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:358)
    at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:3563)
    at android.os.Binder.execTransact(Binder.java:746)
main_musitemaction_play_click (java line: 4742)
java.lang.SecurityException: Permission Denial: opening provider com.android.providers.media.MediaDocumentsProvider from ProcessRecord{311b6e3 9146:myapp/u0a187} (pid=9146, uid=10187) requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs
    at android.os.Parcel.createException(Parcel.java:1950)
    at android.os.Parcel.readException(Parcel.java:1918)
    at android.os.Parcel.readException(Parcel.java:1868)
    at android.app.IActivityManager$Stub$Proxy.getContentProvider(IActivityManager.java:4115)
    at android.app.ActivityThread.acquireProvider(ActivityThread.java:6223)
    at android.app.ContextImpl$ApplicationContentResolver.acquireUnstableProvider(ContextImpl.java:2607)
    at android.content.ContentResolver.acquireUnstableProvider(ContentResolver.java:1828)
    at android.content.ContentResolver.openTypedAssetFileDescriptor(ContentResolver.java:1442)
    at android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:1295)
    at android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:1218)
    at anywheresoftware.b4a.objects.MediaPlayerWrapper.loadAfterReset(MediaPlayerWrapper.java:91)
    at anywheresoftware.b4a.objects.MediaPlayerWrapper.Load(MediaPlayerWrapper.java:66)
    at myapp.main._musitemaction_play_click(main.java:4742)
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:196)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:180)
    at anywheresoftware.b4a.BA.raiseEvent(BA.java:176)
    at anywheresoftware.b4a.objects.ViewWrapper$1.onClick(ViewWrapper.java:80)
    at android.view.View.performClick(View.java:6600)
    at android.view.View.performClickInternal(View.java:6577)
    at android.view.View.access$3100(View.java:781)
    at android.view.View$PerformClick.run(View.java:25917)
    at android.os.Handler.handleCallback(Handler.java:873)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:220)
    at android.app.ActivityThread.main(ActivityThread.java:6929)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:870)
Caused by: android.os.RemoteException: Remote stack trace:
    at com.android.server.am.ActivityManagerService.getContentProviderImpl(ActivityManagerService.java:12853)
    at com.android.server.am.ActivityManagerService.getContentProvider(ActivityManagerService.java:13154)
    at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:358)
    at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:3563)
    at android.os.Binder.execTransact(Binder.java:746)


What causes the different behavior?
 

Attachments

  • 2019-11-10_15-12-34.jpg
    2019-11-10_15-12-34.jpg
    44.8 KB · Views: 346

Erel

B4X founder
Staff member
Licensed User
Longtime User
This is the difference between the test app and the production app, right?
The access given might be temporary, depending on the provider.

If you need persistent access then you can copy the file to a temporary folder or you can let the user choose the folder with ExternalStorage and then choose the actual file in your app. Note that not all providers will be listed with ExternalStorage.
 
Top