0

我正在尝试在 Android 中构建一个音乐应用程序,尽管有时我觉得自己是个可笑的新手。我进行了很多研究,尽我所能了解 MediaPlayer、MediaBrowserCompat / MediaBrowserServiceCompat、MediaController 和 Services 的工作原理,以及数十篇关于如何构建一个不幸的旧教程。

我最大的问题是他们中的大多数人倾向于使用 IBinder 功能和意图来绑定和启动 musicPlaybackService,而 google 的文档使用这些 MediaBrowser 和 MediaBrowserService API,这两种方法都是新的,老实说对我来说非常困难和压倒性的。

到目前为止,我学到了很多东西,但很难。我发现的两个稍微好一点的教程是 https://www.sitepoint.com/a-step-by-step-guide-to-building-an-android-audio-player-app/https:// code.tutsplus.com/tutorials/background-audio-in-android-with-mediasessioncompat--cms-27030他们使用第一种和第二种方法。我的应用程序版本是我通过将我学到的所有部分拼凑而成的。

我自己设法解决了很多错误和问题,但我遇到了一个 NullPointer 异常,我根本不知道如何解决。这个调试器也很奇怪,感觉就像每次错误都来自程序的另一个地方;有时它会在我放置的断点处停止,然后使用完全相同的代码和断点再次运行调试器,它会跳过它们并直接进入运行时错误。

这是我的 Manifest.xml:

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

<permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <service
        android:name=".MediaPlaybackService"
        android:enabled="true"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.MEDIA_BUTTON" />
            <action android:name="android.media.AUDIO_BECOMING_NOISY" />
            <action android:name="android.media.browse.MediaBrowserService" />
        </intent-filter>
    </service>

    <receiver android:name="android.support.v4.media.session.MediaButtonReceiver">
        <intent-filter>
            <action android:name="android.intent.action.MEDIA_BUTTON" />
            <action android:name="android.media.AUDIO_BECOMING_NOISY" />
        </intent-filter>
    </receiver>
</application>

然后这是我的 MainActivity.java

        public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private final int REQUEST_CODE_GESPLAYER_EXTERNAL_STORAGE = 101;
    private static final int STATE_PAUSED = 0;
    private static final int STATE_PLAYING = 1;
    private static int currentState;

    private MediaBrowserCompat gesMediaBrowser;
    private MediaControllerCompat gesMediaController;
    public MediaControllerCompat.TransportControls gesPlaybackController;

    private Button playPauseToggleButton;

    private MediaBrowserCompat.ConnectionCallback mediaBrowserCallbacks = new MediaBrowserCompat.ConnectionCallback() {
        @Override
        public void onConnected() {
            super.onConnected();
            try {
                //create the media controller and register the callbacks to stay in sync
                gesMediaController = new MediaControllerCompat(MainActivity.this, gesMediaBrowser.getSessionToken());
                gesMediaController.registerCallback(mediaControllerCallbacks);

                //save the controller and define the easy access transport controls in the object
                MediaControllerCompat.setMediaController(MainActivity.this, gesMediaController);
                gesPlaybackController = gesMediaController.getTransportControls();

                //Display initial state
                MediaMetadataCompat metadata = gesMediaController.getMetadata();
                PlaybackStateCompat pbState = gesMediaController.getPlaybackState();

            } catch( RemoteException e ) {

            }
        }
        @Override
        public void onConnectionSuspended() {
            // The Service has crashed. Disable transport controls until it automatically reconnects
        }
        @Override
        public void onConnectionFailed() {
            // The Service has refused our connection
            Log.d("onConnectionFail: ", "the service hasn't been able to connect");
        }
    };
    private MediaControllerCompat.Callback mediaControllerCallbacks = new MediaControllerCompat.Callback() {
        @Override
        public void onMetadataChanged(MediaMetadataCompat metadata) {
            super.onMetadataChanged(metadata);
        }
        @Override
        public void onPlaybackStateChanged(PlaybackStateCompat state) {
            super.onPlaybackStateChanged(state);
            if( state == null ) {
                Log.d("onPlaybackChange: ", "the state is null");
                Toast.makeText(MainActivity.this,
                        "onPlaybackStateChange: the state is null",
                        Toast.LENGTH_SHORT)
                        .show();
                return;
            }

            switch( state.getState() ) {
                case PlaybackStateCompat.STATE_PLAYING: {
                    currentState = STATE_PLAYING;
                    break;
                }
                case PlaybackStateCompat.STATE_PAUSED: {
                    currentState = STATE_PAUSED;
                    break;
                }
            }
        }
        @Override
        public void onSessionDestroyed(){
            // Override to handle the session being destroyed
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //grab the buttons for media playback control
        playPauseToggleButton = (Button)findViewById(R.id.playPause_btn);

        //request permissions for external storage
        if (ContextCompat.checkSelfPermission(this,
                android.Manifest.permission.READ_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            // Permission have not been granted
            ActivityCompat.requestPermissions(this,
                    new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE},
                    REQUEST_CODE_GESPLAYER_EXTERNAL_STORAGE);
        }
        else{
            //permissions have already been granted
        }

        //initiate connection to the MediaPlaybackService through MediaBrowser
        gesMediaBrowser = new MediaBrowserCompat(this,
                new ComponentName(this, MediaPlaybackService.class),
                mediaBrowserCallbacks, getIntent().getExtras());
        gesMediaBrowser.connect();

        //Attach listeners to them
        playPauseToggleButton.setOnClickListener(this);
        // space here for other buttons
        // sapce here for other buttons
    }

    @Override
    protected void onStart() {
        super.onStart();
        //gesMediaBrowser.connect();
    }

    /*protected void onStop() {
        super.onStop();
        // (see "stay in sync with the MediaSession")
        if( gesMediaController.getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING ) {
            gesPlaybackController.pause();
        }
        gesMediaBrowser.disconnect();
    }*/
    @Override
    protected void onDestroy() {
        super.onDestroy();
        /*if (gesMediaController != null) {
            gesMediaController.unregisterCallback(mediaControllerCallbacks);
            gesMediaController = null;
        }
        if(gesMediaBrowser != null && gesMediaBrowser.isConnected()) {
            gesMediaBrowser.disconnect();
            gesMediaBrowser = null;
        }*/
        if( gesMediaController.getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING ) {
            gesPlaybackController.pause();
        }

        gesMediaBrowser.disconnect();

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.playPause_btn:
                //has to be dealt with accordingly, based on the current state of mediaplayer
                int currentState = gesMediaController.getPlaybackState().getState();
                if(currentState == PlaybackStateCompat.STATE_PLAYING) {
                    gesPlaybackController.pause();
                } else {
                    //gesPlaybackController.play();
                    gesPlaybackController.playFromMediaId(String.valueOf(R.raw.warner_tautz_off_broadway), null);
                }
                break;
}

    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {
            case REQUEST_CODE_GESPLAYER_EXTERNAL_STORAGE: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(this, "onRequestPermissionResult: granted", Toast.LENGTH_SHORT).show();

                } else {
                    //close the app if permissions aren't granted
                    //Toast.makeText(this, "onRequestPermissionResult: denied", Toast.LENGTH_SHORT).show();
                    finish();
                }
                return;
            }
            // other 'case' lines to check for other
            // permissions this app might request.
        }
    }

然后是playbackService.java

public class MediaPlaybackService extends MediaBrowserServiceCompat implements
    MediaPlayer.OnCompletionListener,

    AudioManager.OnAudioFocusChangeListener {

public static final String COMMAND_EXAMPLE = "command_example";
public static boolean isServiceStarted = false;

/*public int audioIndex;
public ArrayList<Audio> audioList;
public Audio activeAudio;*/

private MediaPlayer gesMediaPlayer;
private MediaSessionCompat gesMediaSession;
private int pausedPosition;

//-------------------------------------Lifecycle methods--------------------------------------//

@Override
public void onCreate() {
    super.onCreate();

    Log.d("onCreate: ", "Service created");
    initMediaPlayer();
    initMediaSession();
    callStateListener();
    registerNoisyReceiver();

    // Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player
    PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder()
            .setActions(
                        PlaybackStateCompat.ACTION_PLAY |
                            PlaybackStateCompat.ACTION_PLAY_PAUSE);
    gesMediaSession.setPlaybackState(playbackStateBuilder.build());
}
@Override
public void onDestroy() {
    super.onDestroy();
    if (gesMediaPlayer != null) {
        gesMediaPlayer.stop();
        gesMediaPlayer.release();
    }

    //Disable the PhoneStateListener
    if (phoneStateListener != null) {
        telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
    }

    removeAudioFocus();
    unregisterReceiver(becomingNoisyReceiver);

    //clear cached playlist
    //new StorageUtils(getApplicationContext()).clearCachedAudioPlaylist();
    NotificationManagerCompat.from(this).cancel(1);
    stopSelf();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Log.d("onStartCommand: ", "Service has been started");
    MediaButtonReceiver.handleIntent(gesMediaSession, intent);
    return super.onStartCommand(intent, flags, startId);
}
@Override
public void onCompletion(MediaPlayer mediaPlayer) {
    if( gesMediaPlayer != null ) {
        gesMediaPlayer.release();
    }
}

//----------------------------------------Initialisers----------------------------------------//

private void initMediaPlayer() {
    gesMediaPlayer = new MediaPlayer();
    gesMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
    gesMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    gesMediaPlayer.setVolume(1.0f, 1.0f);

    /*try {
        //sets songPath as data source for media player
        //gesMediaPlayer.setDataSource(songPath);

        //sets current song as data source for media player
        gesMediaPlayer.setDataSource(activeAudio.getData());
    } catch (IOException e) {
        e.printStackTrace();
        stopSelf();
    }
    gesMediaPlayer.prepareAsync();*/
}
private void initMediaSession() {
    ComponentName mediaButtonReceiver = new ComponentName(getApplicationContext(), MediaButtonReceiver.class);
    gesMediaSession = new MediaSessionCompat(getApplicationContext(), "GESMediaService",
                                                mediaButtonReceiver, null);

    gesMediaSession.setCallback(mediaSessionCallbacks);
    gesMediaSession.setFlags( MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
            MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS );

    //this is for pre-Lollipop media button handling on those devices
    Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
    mediaButtonIntent.setClass(this, MediaButtonReceiver.class);
    PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, 0);
    gesMediaSession.setMediaButtonReceiver(pendingIntent);

    // Set the session's token so that client activities can communicate with it.
    setSessionToken(gesMediaSession.getSessionToken());
}
private void registerNoisyReceiver() {
    //Handles headphones coming unplugged. cannot be done through a manifest receiver
    IntentFilter noisyFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
    registerReceiver(becomingNoisyReceiver, noisyFilter);
}
private void initMediaSessionMetadata() {
    MediaMetadataCompat.Builder metadataBuilder = new MediaMetadataCompat.Builder();

    //Notification icon in card
    metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
    metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));

    //lock screen icon for pre lollipop
    metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
    metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Display Title");
    metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Display Subtitle");
    metadataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, 1);
    metadataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, 1);

    gesMediaSession.setMetadata(metadataBuilder.build());
}
//-----------------------------------Media Playback functions---------------------------------//

//TODO: read about the AssetFileDescriptor, and the ResultReceiver

private MediaSessionCompat.Callback mediaSessionCallbacks = new MediaSessionCompat.Callback() {
    @Override
    public void onPlay() {
        super.onPlay();
        if(!requestAudioFocus()) {
            //failed to gain focus
            return;
        }
        //check if service is started, not only bound
        if(!isServiceStarted){
            startService(new Intent(getApplicationContext(), MediaPlaybackService.class));
        }

        gesMediaSession.setActive(true);
        setMediaPlaybackState(PlaybackStateCompat.STATE_PLAYING);
        showPlayingNotification();
        gesMediaPlayer.start();
    }
    @Override
    public void onPause() {
        super.onPause();

        if( gesMediaPlayer.isPlaying() ) {
            gesMediaPlayer.pause();
            setMediaPlaybackState(PlaybackStateCompat.STATE_PAUSED);
            showPausedNotification();
        }
    }
    @Override
    public void onPlayFromMediaId(String mediaId, Bundle extras) {
        super.onPlayFromMediaId(mediaId, extras);

        try {
            AssetFileDescriptor afd = getResources().openRawResourceFd(Integer.valueOf(mediaId));
            if( afd == null ) {
                Log.d("afd: ", "afd in onPlayFromMediaId is null");
                return;
            }

            try {
                gesMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());

            } catch( IllegalStateException e ) {
                gesMediaPlayer.release();
                initMediaPlayer();
                gesMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
            }

            afd.close();
            initMediaSessionMetadata();

        } catch (IOException e) {
            e.printStackTrace();
            return;
        }

        try {
            gesMediaPlayer.prepare();
        } catch (IOException e) {
            e.printStackTrace();
            Log.d("onPlayFromId: ", "mediaPlayer failed to prepare");
        }

        //Work with extras here if you want
    }
    @Override
    public void onCommand(String command, Bundle extras, ResultReceiver cb) {
        super.onCommand(command, extras, cb);
        if( COMMAND_EXAMPLE.equalsIgnoreCase(command) ) {
            //Custom command here
        }
    }
    @Override
    public void onSeekTo(long pos) {
        super.onSeekTo(pos);
    }
};

private void setMediaPlaybackState(int state) {
    PlaybackStateCompat.Builder playbackstateBuilder = new PlaybackStateCompat.Builder();
    if( state == PlaybackStateCompat.STATE_PLAYING ) {
        playbackstateBuilder.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE |
                                        PlaybackStateCompat.ACTION_PAUSE);
    } else {
        playbackstateBuilder.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE |
                                        PlaybackStateCompat.ACTION_PLAY);
    }
    playbackstateBuilder.setState(state, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 0);
    gesMediaSession.setPlaybackState(playbackstateBuilder.build());
}


//-------------------------------Audio Focus and Calls Handling-------------------------------//

//Handle incoming phone calls
private boolean ongoingCall = false;
private PhoneStateListener phoneStateListener;
private TelephonyManager telephonyManager;
private AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);

@Override
public void onAudioFocusChange(int focusChange) {
    switch( focusChange ) {
        case AudioManager.AUDIOFOCUS_LOSS: {
            // Lost focus for an unbounded amount of time:
            // stop playback and release media player
            if( gesMediaPlayer.isPlaying() ) {
                gesMediaPlayer.stop();
            }
            /*gesMediaPlayer.release();
            gesMediaPlayer = null;*/
            break;
        }
        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: {
            // Lost focus for a short time; Pause only and do not
            // release the media player as playback is likely to resume
            if (gesMediaPlayer.isPlaying()) {
                gesMediaPlayer.pause();
            }
            break;
        }
        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: {
            // Lost focus for a short time (ex. notification sound)
            // but it's ok to keep playing at a temporarily attenuated level
            if( gesMediaPlayer != null ) {
                gesMediaPlayer.setVolume(0.2f, 0.2f);
            }
            break;
        }
        case AudioManager.AUDIOFOCUS_GAIN: {
            //Invoked when the audio focus of the system is updated.
            if( gesMediaPlayer != null ) {
                if( !gesMediaPlayer.isPlaying() ) {
                    gesMediaPlayer.start();
                }
                gesMediaPlayer.setVolume(1.0f, 1.0f);
            }
            break;
        }
    }
}
private boolean requestAudioFocus() {
    AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    int result = audioManager.requestAudioFocus(this,
            AudioManager.STREAM_MUSIC,
            AudioManager.AUDIOFOCUS_GAIN);
    return result == AudioManager.AUDIOFOCUS_GAIN;
}
private boolean removeAudioFocus() {
    return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
            audioManager.abandonAudioFocus(this);
}
private void callStateListener() {
    // Get the telephony manager
    telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
    //Starting listening for PhoneState changes
    phoneStateListener = new PhoneStateListener() {
        @Override
        public void onCallStateChanged(int state, String incomingNumber) {
            switch (state) {
                //if at least one call exists or the phone is ringing
                //pause the MediaPlayer
                case TelephonyManager.CALL_STATE_OFFHOOK:
                case TelephonyManager.CALL_STATE_RINGING:
                    if (gesMediaPlayer != null && gesMediaPlayer.isPlaying()) {
                        gesMediaPlayer.pause();
                        pausedPosition = gesMediaPlayer.getCurrentPosition();
                        ongoingCall = true;
                    }
                    break;
                case TelephonyManager.CALL_STATE_IDLE:
                    // Phone idle. Start/resume playing.
                    if (gesMediaPlayer != null) {
                        if (ongoingCall) {
                            ongoingCall = false;
                            gesMediaPlayer.seekTo(pausedPosition);
                            gesMediaPlayer.start();
                        }
                    }
                    break;
            }
        }
    };
    // Register the listener with the telephony manager
    // Listen for changes to the device call state.
    telephonyManager.listen(phoneStateListener,
            PhoneStateListener.LISTEN_CALL_STATE);
}
private BroadcastReceiver becomingNoisyReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        if( gesMediaPlayer != null && gesMediaPlayer.isPlaying() ) {
            gesMediaPlayer.pause();
        }
    }
};
//------------------------------------Less important methods----------------------------------//

@Nullable
@Override
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) {
    if(TextUtils.equals(clientPackageName, getPackageName())) {
        return new BrowserRoot(getString(R.string.app_name), null);
    }

    return null;
}

//Not important for general audio service, required for class
@Override
public void onLoadChildren(@NonNull String parentId, @NonNull Result<List<MediaBrowserCompat.MediaItem>> result) {
    result.sendResult(null);
}

}

我为整个代码道歉,但在这一点上,我不知道我做错了什么或去哪里看。特别是因为该应用程序确实在以前的版本中工作。如果你们在我的代码中看到任何具体的内容,任何建议都将不胜感激。我尝试在可能的地方捕获异常,但同时我不确定应该将 try-catch 结构放在哪里。我正在努力学习 谢谢大家!

4

0 回答 0