MediaSession 无法在 android 10 及以上版本中播放音频

问题描述 投票:0回答:1

我使用 MediaSessionService 作为前台服务来播放音频(在我的音频播放器 android 项目中)。之前我使用 ExoPlayer 和 Service,当时我使用 ContentResolver 从设备获取(getAudios(Activity Activity) 方法)音频文件,并在 AudioModel 对象中设置音频数据。那时一切都很好,并且在 Android 10 中运行良好。但是当我切换到 MediaSessionService 并按照here指导的所有说明进行操作时,该应用程序在 Android 8 中运行良好,但音频在 Android 10 中无法播放。当我检查日志时我已经看到,缓冲后播放器进入空闲状态,这表明 mediaItem 发生了一些错误(据我所知)。但在 Android 8 中也是如此,即播放器在缓冲状态后进入就绪状态。我想知道Android 10还有其他权限要求吗? 让我再次强调一点,在不使用 MediaSession、MediaController 等的情况下,在 Android 8 和 10 中都可以正常工作。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
    <uses-permission
        android:name="android.permission.READ_EXTERNAL_STORAGE"
        android:maxSdkVersion="32" />
    <uses-permission
        android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="32"
        tools:ignore="ScopedStorage" />
    <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />


    <application
       //..............>

        <service
            android:name=".service.AudioPlaybackService"
            android:exported="true"
            android:foregroundServiceType="mediaPlayback"
            android:permission="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK">
            <intent-filter>
                <action android:name="androidx.media3.session.MediaSessionService" />
            </intent-filter>
        </service>


        <activity
            //............
        </activity>
    </application>

</manifest>
 public MutableLiveData<List<AudioModel>> getAudios(Activity activity) {
        MutableLiveData<List<AudioModel>> mutableLiveData = new MutableLiveData<>();
        List<AudioModel> audioModels = new ArrayList<>();

        if (activity == null) {
            mutableLiveData.setValue(null);
            return mutableLiveData;
        }

        ContentResolver resolver = activity.getContentResolver();
        Uri collections;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            collections = MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL);
        } else collections = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;

        String[] projection = new String[]{
                MediaStore.Audio.Media._ID,
                MediaStore.Audio.Media.DISPLAY_NAME,
                MediaStore.Audio.Media.ALBUM,
                MediaStore.Audio.Media.ARTIST,
                MediaStore.Audio.Media.DATE_ADDED,
                MediaStore.Audio.Media.DURATION,
                MediaStore.Audio.Media.SIZE,
                MediaStore.Audio.Media.DATA
        };

        try (Cursor cursor = resolver.query(collections, projection, null, null, null)) {
            assert cursor != null;
            int idCol = cursor.getColumnIndex(MediaStore.Audio.Media._ID);
            int nameCol = cursor.getColumnIndex(MediaStore.Audio.Media.DISPLAY_NAME);
            int albumCol = cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM);
            int artistCol = cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST);
            int dateAddedCol = cursor.getColumnIndex(MediaStore.Audio.Media.DATE_ADDED);
            int durationCol = cursor.getColumnIndex(MediaStore.Audio.Media.DURATION);
            int sizeCol = cursor.getColumnIndex(MediaStore.Audio.Media.SIZE);
            int dataCol = cursor.getColumnIndex(MediaStore.Audio.Media.DATA);

            while (cursor.moveToNext()) {
                AudioModel model = new AudioModel(Uri.parse(cursor.getString(dataCol)), cursor.getString(nameCol));
                model.setMedia_id(cursor.getString(idCol));
                model.setAlbum(cursor.getString(albumCol));
                model.setArtist(cursor.getString(artistCol));
                model.setDateAdded(cursor.getString(dateAddedCol));
                model.setSize(cursor.getLong(sizeCol));
                model.setDuration(cursor.getLong(durationCol));
                audioModels.add(model);
            }

        } catch (NullPointerException e) {
            Constants.LOG.log("Exception: " + e.getMessage());
        }
        mutableLiveData.setValue(audioModels);
        return mutableLiveData;
    }
@UnstableApi public class MediaSessionCallback implements MediaSession.Callback {

    @NonNull
    @Override
    public ListenableFuture<List<MediaItem>> onAddMediaItems(@NonNull MediaSession mediaSession, @NonNull MediaSession.ControllerInfo controller, @NonNull List<MediaItem> mediaItems) {
        Constants.LOG.mediaSessionLog("MediaSession > onAddMediaItems, size -> "+mediaItems.size());

        List<MediaItem> updatedMediaItems = mediaItems.stream().peek(
                mediaItem ->
                mediaItem.buildUpon()
                .setUri(mediaItem.requestMetadata.mediaUri)
                .build()).collect(Collectors.toList());

        return Futures.immediateFuture(updatedMediaItems);
    }



    @NonNull
    @OptIn(markerClass = UnstableApi.class) @Override
    public ListenableFuture<MediaSession.MediaItemsWithStartPosition> onSetMediaItems(@NonNull MediaSession mediaSession, @NonNull MediaSession.ControllerInfo controller, @NonNull List<MediaItem> mediaItems, int startIndex, long startPositionMs) {
        Constants.LOG.mediaSessionLog("MediaSession > onAddMediaItems, size > "+mediaItems.size()+", startIndex > "+startIndex);
        return MediaSession.Callback.super.onSetMediaItems(mediaSession, controller, mediaItems, startIndex, startPositionMs);
    }

    
}

public class AudioPlaybackService extends MediaSessionService {
    private ExoPlayer player;
    private MediaSession mediaSession = null;

    // Create your Player and MediaSession in the onCreate lifecycle event
    @OptIn(markerClass = UnstableApi.class) @Override
    public void onCreate() {
        super.onCreate();
        if (this.player == null) this.player = new ExoPlayer.Builder(this).build();
        this.mediaSession = new MediaSession.Builder(this, player)
                .setCallback(new MediaSessionCallback())
                .build();
    }

    // The user dismissed the app from the recent tasks
    @Override
    public void onTaskRemoved(@Nullable Intent rootIntent) {
        Player player = this.mediaSession.getPlayer();
        if (!player.getPlayWhenReady()
                || player.getMediaItemCount() == 0
                || player.getPlaybackState() == Player.STATE_ENDED) {
            // Stop the service if not playing, continue playing in the background
            // otherwise.
            stopSelf();
        }
    }


    @Nullable
    @Override
    public MediaSession onGetSession(@NonNull MediaSession.ControllerInfo controllerInfo) {
        return this.mediaSession;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);
        if (intent == null || intent.getAction() == null) return Service.START_NOT_STICKY;

        String action = intent.getAction();

        this.player = PlayerCreator.getPlayer(getApplicationContext());

        switch (action) {
            case Constants.Service.START_AUDIO_PLAYBACK_FOREGROUND:
                startForeground(Constants.Notification.AUDIO_PLAYING_NOTIFICATION_CHANNEL_ID, createNotification());
                playMusic();
                break;
            case Constants.Service.STOP_AUDIO_PLAYBACK_FOREGROUND:
                stopForeground(true);
                playPause();
                stopSelf();
                break;
            case Constants.Service.PLAY_PAUSE_AUDIO_PLAYBACK_FOREGROUND:
                startForeground(Constants.Notification.AUDIO_PLAYING_NOTIFICATION_CHANNEL_ID, createNotification());
                playPause();
                break;
            case Constants.Service.NEXT_AUDIO_PLAYBACK_FOREGROUND:
                startForeground(Constants.Notification.AUDIO_PLAYING_NOTIFICATION_CHANNEL_ID, createNotification());
                next();
                break;
            case Constants.Service.PREVIOUS_AUDIO_PLAYBACK_FOREGROUND:
                startForeground(Constants.Notification.AUDIO_PLAYING_NOTIFICATION_CHANNEL_ID, createNotification());
                previous();
                break;
            case Constants.Service.MEDIA_ITEM_TRANSITION:
                startForeground(Constants.Notification.AUDIO_PLAYING_NOTIFICATION_CHANNEL_ID, createNotification());
                break;
        }
        return START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        this.mediaSession.getPlayer().release();
        this.mediaSession.release();
        this.mediaSession = null;
        super.onDestroy();
    }
}

setUpPlayer 是用于将 mediaItems 设置为播放器的方法。它从模型类中提取 id 和 uri 并创建一个 mediaItem,然后设置为 MediaController

 @OptIn(markerClass = UnstableApi.class)
    private void setUpPlayer(List<AudioModel> audioModels) {
        Constants.LOG.log("In setup player");
        if (this.binding == null) this.onDestroy(); // TODO: Destroying the fragment

        // Initialize Exoplayer;
        this.mediaController.addListener(this);

        // Setting the ExoPlayer MediaItems / MediaSources
        List<MediaItem> mediaItemList = new ArrayList<>();
        for (AudioModel file : audioModels) {
            Constants.LOG.mediaSessionLog("AudioModel URI : "+file.getData());
            MediaItem item = new MediaItem.Builder().setMediaId(file.getMedia_id()).setUri(file.getData()).build();
            Constants.LOG.mediaSessionLog("Audio URI : "+item.requestMetadata.mediaUri);
            mediaItemList.add(item);
        }

        this.mediaController.setMediaItems(mediaItemList);
        this.mediaController.prepare();
        this.binding.progressBarAudioFileLoad.setVisibility(View.GONE);
    }

我在这里请求权限

public class HomeActivity extends AppCompatActivity {

    private String[] permissions;
    /**
     * settingOpenResultLauncher is used to open setting for permission grant and check the result
     */
    private ActivityResultLauncher<Intent> settingOpenResultLauncher;

    private ActivityHomeBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Constants.LOG.lifeCycleLog(this.getClass().getSimpleName()+this.INSTANCE_ID+": onCreate");
        // Handle the splash screen transition.
        SplashScreen.installSplashScreen(this);

        super.onCreate(savedInstanceState);
        //Do the below before initializing binding
        this.permissions = new String[]{Manifest.permission.READ_EXTERNAL_STORAGE};
        if(!isPermissionsGranted()) requestPermission(permissions);

        this.settingOpenResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
                result -> {
                    if(!isPermissionsGranted()) requestPermission(HomeActivity.this.permissions);
                    else finish();
                });
    }

    private void requestPermission(String... permissions) {
        // TODO: add other permissions which are needed
        ActivityCompat.requestPermissions(HomeActivity.this,
                permissions,
                Constants.PERMISSIONS.PERMISSIONS_CODE);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == Constants.PERMISSIONS.PERMISSIONS_CODE) {
            int i = 0;
            for (; i < grantResults.length; i++) {
                if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
                    if(!shouldShowRequestPermissionRationale(permissions[i])) {
                        // This executes when second time requesting the permission
                        // TODO: Show any alert that why the permission(s) should be granted
                        openSetting();
                    } else requestPermission(permissions[i]);
                }
            }
        }
    }

    /**
     * This method is used to open settings when user selects don't show permission asking again
     */
    private void openSetting() {
        Intent i = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        Uri uri = Uri.fromParts("package", getPackageName(), null);
        i.setData(uri);
        if (this.settingOpenResultLauncher == null) {
            Toast.makeText(this, "Unable to open setting", Toast.LENGTH_SHORT).show();
            return;
        }
        this.settingOpenResultLauncher.launch(i);
    }

    private boolean isPermissionsGranted() {
        String[] permissions;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            permissions = new String[]{Manifest.permission.READ_MEDIA_AUDIO,
                    Manifest.permission.READ_MEDIA_VIDEO};
        } else {
            permissions = new String[]{Manifest.permission.READ_EXTERNAL_STORAGE};
        }

        // TODO: for android 13+ which permissions are not handled, handle those below
        for (String permission : permissions)
            if (ContextCompat.checkSelfPermission(HomeActivity.this, permission)
                    == PackageManager.PERMISSION_DENIED)
                return false;
        return true;
    }

}
android android-mediasession android-media3 mediasession
1个回答
0
投票

您遇到的错误可能是由于 Android 版本 10 及更高版本中引入了存储访问框架 (SAF),无法使用代码访问下载文件夹中的文件。这有助于使用户设备更加安全。

private void loadAudioFromDownloadFolder() {
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    intent.setType("audio/*");
    intent.setData(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS));

    startActivityForResult(intent, REQUEST_CODE_FILE_PICKER);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == REQUEST_CODE_FILE_PICKER && resultCode == RESULT_OK) {
        Uri selectedFileUri = data.getData();
        MediaItem mediaItem = new MediaItem.Builder()
            .setUri(selectedFileUri)
            .build();
        player.addMediaItem(mediaItem);
    }
}

Android 开发者文档:

https://developer.android.com/guide/topics/providers/document-provider

https://developer.android.com/training/data-storage/shared/documents-files

中文章:

https://developer.android.com/training/data-storage/shared/media

© www.soinside.com 2019 - 2024. All rights reserved.