Java安卓媒体播放器(通知)

huangapple go评论63阅读模式
英文:

Java Android Media Player (Notification)

问题

以下是您提供的代码的翻译部分:

package com.radiomedia.a1liferadio;

import androidx.appcompat.app.AppCompatActivity;

import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private ImageView imagePlayPause;
    private TextView textCurrentTime, textTotalDuration;
    private SeekBar playerSeekBar;
    private MediaPlayer mediaPlayer;
    private Handler handler = new Handler();

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

        imagePlayPause = findViewById(R.id.imagePlayPause);
        textCurrentTime = findViewById(R.id.textCurrentTime);
        textTotalDuration = findViewById(R.id.textTotalDuration);
        playerSeekBar = findViewById(R.id.playerSeekBar);
        mediaPlayer = new MediaPlayer();

        playerSeekBar.setMax(100);

        imagePlayPause.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mediaPlayer.isPlaying()) {
                    handler.removeCallbacks(updater);
                    mediaPlayer.pause();
                    imagePlayPause.setImageResource(R.drawable.ic_play);
                } else {
                    mediaPlayer.start();
                    imagePlayPause.setImageResource(R.drawable.ic_pause);
                    updateSeekBar();
                }
            }
        });

        prepareMediaPlayer();
    }

    private void prepareMediaPlayer() {
        try {
            mediaPlayer.setDataSource("http://stream.radiomedia.com.au:8003/stream"); // 媒体的URL
            mediaPlayer.prepare();
            textTotalDuration.setText(milliSecondsToTimer(mediaPlayer.getDuration()));
        } catch (Exception exception) {
            Toast.makeText(this, exception.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }

    private Runnable updater = new Runnable() {
        @Override
        public void run() {
            updateSeekBar();
            long currentDuration = mediaPlayer.getCurrentPosition();
            textCurrentTime.setText(milliSecondsToTimer(currentDuration));
        }
    };

    private void updateSeekBar() {
        if (mediaPlayer.isPlaying()) {
            playerSeekBar.setProgress((int) (((float) mediaPlayer.getCurrentPosition() / mediaPlayer.getDuration()) * 100));
            handler.postDelayed(updater, 1000);
        }
    }

    private String milliSecondsToTimer(long milliSeconds) {
        String timerString = "";
        String secondsString;

        int hours = (int) (milliSeconds / (1000 * 60 * 60));
        int minutes = (int) (milliSeconds % (1000 * 60 * 60)) / (1000 * 60);
        int seconds = (int) ((milliSeconds % (1000 * 60 * 60)) % (1000 * 60) / 1000);

        if (hours > 0) {
            timerString = hours + ":";
        }

        if (seconds < 10) {
            secondsString = "0" + seconds;
        } else {
            secondsString = "" + seconds;
        }

        timerString = timerString + minutes + ":" + secondsString;
        return timerString;
    }
}

请注意,我已经为您提供了代码的翻译,不包括原始代码中的注释部分。如果您有任何进一步的疑问,请随时问我。

英文:

So I have the following code and it is working fine, however I want to show the media controls in the users Notification area so they can play and stop the music as they please while the app is in the background.

I am wondering how one does this?

Code:

package com.radiomedia.a1liferadio;
import androidx.appcompat.app.AppCompatActivity;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private ImageView imagePlayPause;
private TextView textCurrentTime, textTotalDuration;
private SeekBar playerSeekBar;
private MediaPlayer mediaPlayer;
private Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imagePlayPause = findViewById(R.id.imagePlayPause);
textCurrentTime = findViewById(R.id.textCurrentTime);
textTotalDuration = findViewById(R.id.textTotalDuration);
playerSeekBar = findViewById(R.id.playerSeekBar);
mediaPlayer = new MediaPlayer();
playerSeekBar.setMax(100);
imagePlayPause.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(mediaPlayer.isPlaying())
{
handler.removeCallbacks(updater);
mediaPlayer.pause();
imagePlayPause.setImageResource(R.drawable.ic_play);
}else{
mediaPlayer.start();
imagePlayPause.setImageResource(R.drawable.ic_pause);
updateSeekBar();
}
}
});
prepareMediaPlayer();
}
private  void prepareMediaPlayer() {
try {
mediaPlayer.setDataSource(&quot;http://stream.radiomedia.com.au:8003/stream&quot;); //url of media
mediaPlayer.prepare();
textTotalDuration.setText(milliSecondsToTimer(mediaPlayer.getDuration()));
} catch (Exception exception){
Toast.makeText(this,exception.getMessage(), Toast.LENGTH_SHORT).show();
}
};
private Runnable updater = new Runnable() {
@Override
public void run() {
updateSeekBar();
long currentDuration = mediaPlayer.getCurrentPosition();
textCurrentTime.setText(milliSecondsToTimer(currentDuration));
}
};
private void updateSeekBar(){
if(mediaPlayer.isPlaying()) {
playerSeekBar.setProgress((int) (((float) mediaPlayer.getCurrentPosition() / mediaPlayer.getDuration()) * 100));
handler.postDelayed(updater, 1000);
}
};
private String milliSecondsToTimer(long milliSeconds) {
String timerString = &quot;&quot;;
String secondsString;
int hours = (int)(milliSeconds / (1000 * 60 * 60));
int minutes = (int)(milliSeconds % (1000 * 60 * 60)) / (1000 *60);
int seconds = (int)((milliSeconds % (1000 * 60 *60)) % (1000 * 60) / 1000);
if(hours &gt; 0)
{
timerString = hours + &quot;:&quot;;
}
if(seconds &lt; 10)
{
secondsString = &quot;0&quot; + seconds;
}else{
secondsString = &quot;&quot; + seconds;
}
timerString = timerString + minutes + &quot;:&quot; + secondsString;
return timerString;
}
}

答案1

得分: 13

以下是翻译好的部分:

你应该遵循这个旧的 Google MediaPlayer 示例(如果主版本对你造成崩溃,请使用这个提交),如果你想继续使用 MediaPlayer。

但是,如果你不介意使用 ExoPlayer,那么你应该改为遵循这个新的示例,因为 ExoPlayer 已经可以处理部分通知相关的事务。

但是在这些示例项目中,你可以看到:

  1. 这不仅仅是一个简单的通知,它还与用户在耳机上按下播放按钮等操作相关联。
  2. 你真的需要一个服务来处理这个,以绑定到通知,并处理 action.MEDIA_BUTTON 意图。

添加带有 MediaSession 和 Service 的最小代码(不是使用 ExoPlayer,而是使用你正在使用的 MediaPlayer)如下所示:

在你的应用的 build.gradle 中添加 media compat 库的依赖:

dependencies {
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation "androidx.media:media:1.1.0"
}

然后创建一个用于创建通知的类:

// 这里是 MediaNotificationManager 类的代码...

接着,创建一个处理播放和通知按钮点击的 Service 类:

// 这里是 MediaSessionService 类的代码...

更新你的清单文件以声明这个服务并将 action.MEDIA_BUTTON 意图发送到它:

<!-- 这是清单文件中的一部分... -->

然后从你的主 Activity 中启动这个服务:

ContextCompat.startForegroundService(
        MainActivity.this.getApplicationContext(),
        new Intent(MainActivity.this.getApplicationContext(), MediaSessionService.class));

这里仍然需要做的事情包括使你的主 Activity 将操作发送到你的服务或服务中的播放器,根据变化更新通知和服务状态,但是你应该能够通过这段代码实现一些功能,并从 Google 示例代码中找到其他部分的解决方案。

英文:

You should follow this old google mediaplayer sample (Use this commit if the master version crashes for you) if you want to keep using the MediaPlayer.

But If you don't mind using ExoPlayer, then you should instead follow this new one, since ExoPlayer can already take care of part of the notification stuff.

But so in these sample projects you can see that:

  1. It's not as simple as just a notification, it also ties into people pressing on a play button on their headset etc.
  2. You really need a service for this, to bind to the notification, to handle the action.MEDIA_BUTTON intents.

Minimal code for adding such a notification with a MediaSession and Service with the MediaPlayer (so not ExoPlayer) like you are using would look something like this:
Add the media compat libraries to your apps' build gradle:

dependencies {
implementation &#39;androidx.appcompat:appcompat:1.1.0&#39;
implementation &quot;androidx.media:media:1.1.0&quot;
}

Then create a class for creating the Notification:

package com.radiomedia.a1liferadio;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.graphics.Color;
import android.os.Build;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import androidx.media.app.NotificationCompat.MediaStyle;
import androidx.media.session.MediaButtonReceiver;
/**
* Keeps track of a notification and updates it automatically for a given MediaSession. This is
* required so that the music service don&#39;t get killed during playback.
*/
public class MediaNotificationManager {
public static final int NOTIFICATION_ID = 412;
private static final String TAG = MediaNotificationManager.class.getSimpleName();
private static final String CHANNEL_ID = &quot;com.example.android.musicplayer.channel&quot;;
private static final int REQUEST_CODE = 501;
private final MediaSessionService mService;
private final NotificationCompat.Action mPlayAction;
private final NotificationCompat.Action mPauseAction;
private final NotificationManager mNotificationManager;
public MediaNotificationManager(MediaSessionService musicContext) {
mService = musicContext;
mNotificationManager =
(NotificationManager) mService.getSystemService(Service.NOTIFICATION_SERVICE);
mPlayAction =
new NotificationCompat.Action(
R.drawable.ic_play,
&quot;play&quot;,
MediaButtonReceiver.buildMediaButtonPendingIntent(
mService,
PlaybackStateCompat.ACTION_PLAY));
mPauseAction =
new NotificationCompat.Action(
R.drawable.ic_pause,
&quot;pause&quot;,
MediaButtonReceiver.buildMediaButtonPendingIntent(
mService,
PlaybackStateCompat.ACTION_PAUSE));
// Cancel all notifications to handle the case where the Service was killed and
// restarted by the system.
mNotificationManager.cancelAll();
}
public void onDestroy() {
Log.d(TAG, &quot;onDestroy: &quot;);
}
public NotificationManager getNotificationManager() {
return mNotificationManager;
}
public Notification getNotification(MediaMetadataCompat metadata,
@NonNull PlaybackStateCompat state,
MediaSessionCompat.Token token) {
boolean isPlaying = state.getState() == PlaybackStateCompat.STATE_PLAYING;
MediaDescriptionCompat description = metadata.getDescription();
NotificationCompat.Builder builder =
buildNotification(state, token, isPlaying, description);
return builder.build();
}
private NotificationCompat.Builder buildNotification(@NonNull PlaybackStateCompat state,
MediaSessionCompat.Token token,
boolean isPlaying,
MediaDescriptionCompat description) {
// Create the (mandatory) notification channel when running on Android Oreo.
if (isAndroidOOrHigher()) {
createChannel();
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(mService, CHANNEL_ID);
builder.setStyle(
new MediaStyle()
.setMediaSession(token)
.setShowActionsInCompactView(0)
// For backwards compatibility with Android L and earlier.
.setShowCancelButton(true)
.setCancelButtonIntent(
MediaButtonReceiver.buildMediaButtonPendingIntent(
mService,
PlaybackStateCompat.ACTION_STOP)))
.setColor(ContextCompat.getColor(mService, R.color.colorPrimary))
.setSmallIcon(R.drawable.ic_play)
// Pending intent that is fired when user clicks on notification.
.setContentIntent(createContentIntent())
// Title - Usually Song name.
.setContentTitle(description.getTitle())
// When notification is deleted (when playback is paused and notification can be
// deleted) fire MediaButtonPendingIntent with ACTION_PAUSE.
.setDeleteIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(
mService, PlaybackStateCompat.ACTION_PAUSE));
builder.addAction(isPlaying ? mPauseAction : mPlayAction);
return builder;
}
// Does nothing on versions of Android earlier than O.
@RequiresApi(Build.VERSION_CODES.O)
private void createChannel() {
if (mNotificationManager.getNotificationChannel(CHANNEL_ID) == null) {
// The user-visible name of the channel.
CharSequence name = &quot;MediaSession&quot;;
// The user-visible description of the channel.
String description = &quot;MediaSession and MediaPlayer&quot;;
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID, name, importance);
// Configure the notification channel.
mChannel.setDescription(description);
mChannel.enableLights(true);
// Sets the notification light color for notifications posted to this
// channel, if the device supports this feature.
mChannel.setLightColor(Color.RED);
mChannel.enableVibration(true);
mChannel.setVibrationPattern(
new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
mNotificationManager.createNotificationChannel(mChannel);
Log.d(TAG, &quot;createChannel: New channel created&quot;);
} else {
Log.d(TAG, &quot;createChannel: Existing channel reused&quot;);
}
}
private boolean isAndroidOOrHigher() {
return Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.O;
}
private PendingIntent createContentIntent() {
Intent openUI = new Intent(mService, MainActivity.class);
openUI.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
return PendingIntent.getActivity(
mService, REQUEST_CODE, openUI, PendingIntent.FLAG_CANCEL_CURRENT);
}
}

Then Create a Service class that handles playback and the notification button presses:

package com.radiomedia.a1liferadio;
import android.app.Notification;
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.IBinder;
import android.os.SystemClock;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.view.KeyEvent;
import androidx.annotation.Nullable;
public class MediaSessionService extends Service {
public MediaPlayer mediaPlayer;
public static final String TAG = &quot;MediaSessionService&quot;;
public static final int NOTIFICATION_ID = 888;
private MediaNotificationManager mMediaNotificationManager;
private MediaSessionCompat mediaSession;
@Override
public void onCreate() {
super.onCreate();
mediaPlayer = new MediaPlayer();
mMediaNotificationManager = new MediaNotificationManager(this);
mediaSession = new MediaSessionCompat(this, &quot;SOME_TAG&quot;);
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mediaSession.setCallback(new MediaSessionCompat.Callback() {
@Override
public void onPlay() {
mediaPlayer.start();
}
@Override
public void onPause() {
mediaPlayer.pause();
}
});
Notification notification =
mMediaNotificationManager.getNotification(
getMetadata(), getState(), mediaSession.getSessionToken());
startForeground(NOTIFICATION_ID, notification);
}
public MediaMetadataCompat getMetadata() {
MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder();
builder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, &quot;artist&quot;);
builder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, &quot;title&quot;);
builder.putLong(
MediaMetadataCompat.METADATA_KEY_DURATION, mediaPlayer.getDuration()
);
return builder.build();
}
private PlaybackStateCompat getState() {
long actions = mediaPlayer.isPlaying() ? PlaybackStateCompat.ACTION_PAUSE : PlaybackStateCompat.ACTION_PLAY;
int state = mediaPlayer.isPlaying() ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED;
final PlaybackStateCompat.Builder stateBuilder = new PlaybackStateCompat.Builder();
stateBuilder.setActions(actions);
stateBuilder.setState(state,
mediaPlayer.getCurrentPosition(),
1.0f,
SystemClock.elapsedRealtime());
return stateBuilder.build();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (&quot;android.intent.action.MEDIA_BUTTON&quot;.equals(intent.getAction())) {
KeyEvent keyEvent = (KeyEvent) intent.getExtras().get(&quot;android.intent.extra.KEY_EVENT&quot;);
if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PAUSE) {
mediaPlayer.pause();
} else {
mediaPlayer.start();
}
}
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}

Update your manifest for declaring the service and sending the action.MEDIA_BUTTON intent to it.

&lt;service
android:name=&quot;.MediaSessionService&quot;
android:enabled=&quot;true&quot;
android:exported=&quot;true&quot;&gt;
&lt;intent-filter&gt;
&lt;action android:name=&quot;android.intent.action.MEDIA_BUTTON&quot;/&gt;
&lt;/intent-filter&gt;
&lt;/service&gt;
&lt;receiver android:name=&quot;androidx.media.session.MediaButtonReceiver&quot;&gt;
&lt;intent-filter&gt;
&lt;action android:name=&quot;android.intent.action.MEDIA_BUTTON&quot;/&gt;
&lt;/intent-filter&gt;
&lt;/receiver&gt;

This MediaButtonReceiver above is needed for pre-OREO devices, it will forward the event to the service on those platforms.

Then start the service from your main activity.

ContextCompat.startForegroundService(
MainActivity.this.getApplicationContext(),
new Intent(MainActivity.this.getApplicationContext(), MediaSessionService.class));

Things still to do here are making your main activity send actions to your service or the player in the service somehow, updating the notification and service state on a change, but you should be able to get something working with this code and figure out the rest from the google sample code.

答案2

得分: 1

有两种情况:

第一种情况:

  1. 如果你想在后台播放歌曲,你需要启动一个前台服务。前台服务附带有通知。(将进度条位置、播放暂停状态、播放列表或视频网址传递给服务)。

  2. 在你创建自定义通知并带有媒体控制按钮后,你需要做的是:

  3. 你还需要在服务中添加一个媒体播放器,并将你的参数(如位置、播放暂停状态)传递给服务中的播放器,以便它可以从之前的活动播放器状态继续播放。

  4. 当点击通知时,你需要将相同的参数传递给活动。

第二种情况:

通过连接播放器启动前台服务,并将活动与其绑定,在通知和活动中同时处理所有控制。

  • 你可以将播放器控制传递给通知,从而在前台服务中启动并在后台播放歌曲。

  • 你可以使用偏好设置或 TinyDB 来获得最佳性能,以在第一种情况下获取要传递的参数。

英文:

There are two scenarios :

First

  1. if you want to play song in background you should have to start a forground service. forground service is with notification .(pass the seekbar position, play pause state, playlist or video url to service).

  2. After you creating custome notification with media controls whatever you want

  3. you have to add a media player to service also pass your parameters like position , play pause state to service player that it play from your previous state of activity player.

  4. on click of notification you have to do same parameters pass to activity .

Second

Start forground service with attaching a player and bind the activity to it, and handle all controls from notification and activity at the same time.

  • you can pass player controls to notfication in sense of froground service to start and play song in background .

  • you can use preference or tinydb for best performance in getting parameters to pass in first case.

huangapple
  • 本文由 发表于 2020年8月20日 16:43:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/63501425.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定