我正在尝试在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:
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);
}
然后这是我的MainActivity.java
class Postlist extends React.Component {
state = {
posts: [],
comments: []
};
componentDidMount() {
axios.get(`https://jsonplaceholder.typicode.com/posts`).then(res => {
const posts = res.data;
this.setState({ posts });
});
axios.get(`https://jsonplaceholder.typicode.com/comments`).then(res => {
const comments = res.data;
this.setState({ comments });
});
}
render() {
return (
<div className="container">
<div className="jumbotron-div col s12">
<ul className="collection">
{this.state.posts.map(post =>
(
<div>
<li
key={post.id}
className="collection-item left-align red lighten-3"
>
<h5>User ID: {post.id}</h5>
<p>Post: {post.body}</p>
</li>
<div className="jumbotron-div col s12">
<ul className="collection">
{
this.state.comments.map(comment => (
<li
key={comment.id}
className="collection-item left-align purple lighten-2"
>
<p>User ID: {comment.id}</p>
<p>Post: {comment.body}</p>
</li>
)
)
}
</ul>
</div>
</div>
))
}
</ul>
</div>
</div>
);
}
}
ReactDOM.render(
<Postlist name="World" />,
document.getElementById('container')
);
然后是playbackService.java
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
}
我为整个代码道歉,但此时我不知道我在做什么错误或在哪里看。特别是因为该应用程序确实在以前的版本中工作。如果你们在我的代码中看到任何特定内容,那么任何建议都会很受欢我尝试尽可能地捕获异常,但同时我不知道我应该在哪里放置try-catch构造。我正在尝试学习 谢谢任何人!