从服务中将 PlayerView 与 SimpleExoPlayer 绑定

Binding PlayerView with SimpleExoPlayer from a service

我已经为 运行 后台音频实施了一项服务,运行 非常完美,但我无法从该服务获取 SimpleExoPlayer 的实例到 activity更新 UI,如果我退出并重新打开 activity,音频也会在后台播放两次。

AudioPlayerService

public class AudioPlayerService extends Service {

    private final IBinder mBinder = new LocalBinder();
    private SimpleExoPlayer player;
    private Item item;
    private PlayerNotificationManager playerNotificationManager;

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

    @Override
    public void onDestroy() {
        playerNotificationManager.setPlayer(null);
        player.release();
        player = null;
        super.onDestroy();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
    enter code here
    public SimpleExoPlayer getplayerInstance() {
        return player;
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Bundle b = intent.getBundleExtra(AppConstants.BUNDLE_KEY);
        if (b != null) {
            item = b.getParcelable(AppConstants.ITEM_KEY);
        }
        startPlayer();
        return START_STICKY;
    }

    private void startPlayer() {
        final Context context = this;
        Uri uri = Uri.parse(item.getUrl());
        player = ExoPlayerFactory.newSimpleInstance(context, new DefaultTrackSelector());
        DefaultDataSourceFactory dataSourceFactory = new DefaultDataSourceFactory(context,
                Util.getUserAgent(context, getString(R.string.app_name)));
        MediaSource mediaSource = new ExtractorMediaSource.Factory(dataSourceFactory)
                .createMediaSource(uri);
        player.prepare(mediaSource);
        player.setPlayWhenReady(true);
        playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(context, AppConstants.PLAYBACK_CHANNEL_ID,
                R.string.playback_channel_name,
                AppConstants.PLAYBACK_NOTIFICATION_ID,
                new PlayerNotificationManager.MediaDescriptionAdapter() {
                    @Override
                    public String getCurrentContentTitle(Player player) {
                        return item.getTitle();
                    }

                    @Nullable
                    @Override
                    public PendingIntent createCurrentContentIntent(Player player) {
                        Intent intent = new Intent(context, PlayerActivity.class);
                        Bundle serviceBundle = new Bundle();
                        serviceBundle.putParcelable(AppConstants.ITEM_KEY, item);
                        intent.putExtra(AppConstants.BUNDLE_KEY, serviceBundle);
                        return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
                    }

                    @Nullable
                    @Override
                    public String getCurrentContentText(Player player) {
                        return item.getSummary();
                    }

                    @Nullable
                    @Override
                    public Bitmap getCurrentLargeIcon(Player player, PlayerNotificationManager.BitmapCallback callback) {
                        return item.getBitmap();
                    }
                }
        );
        playerNotificationManager.setNotificationListener(new PlayerNotificationManager.NotificationListener() {
            @Override
            public void onNotificationStarted(int notificationId, Notification notification) {
                startForeground(notificationId, notification);
            }

            @Override
            public void onNotificationCancelled(int notificationId) {
                stopSelf();
            }
        });
        playerNotificationManager.setPlayer(player);
    }

    public class LocalBinder extends Binder {
        public AudioPlayerService getService() {
            return AudioPlayerService.this;
        }
    }
}

这是我的 activity 启动服务并绑定到它的地方。我必须按顺序将 Item 对象传递给 运行,如果我不使用意图传递数据,服务将崩溃,所以我不能在服务本身中执行 startService() 我必须我想从 activity 开始。

PlayerActivity

public class PlayerActivity extends BaseActivity {

    @BindView(R.id.video_view)
    PlayerView mPlayerView;
    @BindView(R.id.tvTitle)
    TextView mTvTitle;
    @BindView(R.id.tvSummary)
    TextView mTvSummary;
    @BindView(R.id.ivThumbnail)
    ImageView mIvThumb;
    private SimpleExoPlayer player;
    private String mURL, mTitle, mSummary, mImage;
    private AudioPlayerService mService;
    private boolean mBound = false;
    private Intent intent;

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            AudioPlayerService.LocalBinder binder = (AudioPlayerService.LocalBinder) iBinder;
            mService = binder.getService();
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {


mBound = false;
        }
    };

    @SuppressLint("MissingSuperCall")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        onCreate(savedInstanceState, R.layout.activity_player);
        Bundle b = getIntent().getBundleExtra(AppConstants.BUNDLE_KEY);
        if (b != null) {
            Item item = b.getParcelable(AppConstants.ITEM_KEY);
            mURL = item.getUrl();
            mImage = item.getImage();
            mTitle = item.getTitle();
            mSummary = item.getSummary();
            intent = new Intent(this, AudioPlayerService.class);
            Bundle serviceBundle = new Bundle();
            serviceBundle.putParcelable(AppConstants.ITEM_KEY, item);
            intent.putExtra(AppConstants.BUNDLE_KEY, serviceBundle);
            Util.startForegroundService(this, intent);

        }
    }

    private void initializePlayer() {
        if (player == null && !mURL.isEmpty() && mBound) {
            player = mService.getplayerInstance();
            mPlayerView.setPlayer(player);
            mPlayerView.setControllerHideOnTouch(false);
            mPlayerView.setControllerShowTimeoutMs(10800000);
        }
    }

    @Override
    public void onStart() {
        super.onStart();
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        initializePlayer();
        setUI();
    }

    private void setUI() {
        mTvTitle.setText(mTitle);
        mTvSummary.setText(mSummary);
        GlideApp.with(this)
                .load(mImage)
                .placeholder(R.color.colorPrimary)
                .diskCacheStrategy(DiskCacheStrategy.ALL)
                .into(mIvThumb);
    }

    @Override
    protected void onStop() {
        unbindService(mConnection);
        mBound = false;
        releasePlayer();
        super.onStop();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.player_menu, menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.share_podcast:
                //Logic for Share
                return true;
            case R.id.download_podcast:
                //Logic for download
                return true;
            case android.R.id.home:
                onBackPressed();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    private void releasePlayer() {
        if (player != null) {
            player.release();
            player = null;
        }
    }

    @Override
    public void onToolBarSetUp(Toolbar toolbar, ActionBar actionBar) {
        TextView tvHeader = toolbar.findViewById(R.id.tvClassName);
        tvHeader.setText(R.string.app_name);
        actionBar.setHomeAsUpIndicator(R.drawable.ic_arrow_back_black_24dp);
    }
}

我已经尝试了我所知道的一切,但因此我无法前进。

因此,经过大量研究,我能够使用绑定服务并从该服务获取 SimpleExoPlayer 实例并使用以下方法将播放器视图设置为始终显示来解决此问题。

mPlayerView.showController()

经过所有的修改和设置后,只需要两个类就可以实现带有通知控制的后台音频播放,一个是activity,另一个是使用最新的exoplayer版本的服务。

PlayerActivity

    public class PlayerActivity extends BaseActivity {

    @BindView(R.id.video_view)
    PlayerView mPlayerView;
    @BindView(R.id.tvTitle)
    TextView mTvTitle;
    @BindView(R.id.tvSummary)
    TextView mTvSummary;
    @BindView(R.id.ivThumbnail)
    ImageView mIvThumb;
    private String mUrl, mTitle, mSummary, mImage;
    private AudioPlayerService mService;
    private Intent intent;
    private String shareableLink;
    private boolean mBound = false;

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            AudioPlayerService.LocalBinder binder = (AudioPlayerService.LocalBinder) iBinder;
            mService = binder.getService();
            mBound = true;
            initializePlayer();
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            mBound = false;
        }
    };

    @SuppressLint("MissingSuperCall")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        onCreate(savedInstanceState, R.layout.activity_player);
        Bundle b = getIntent().getBundleExtra(AppConstants.BUNDLE_KEY);
        if (b != null) {
            Item item = b.getParcelable(AppConstants.ITEM_KEY);
            shareableLink = b.getString(AppConstants.SHARE_KEY);
            mImage = item.getImage();
            mUrl = item.getUrl();
            mTitle = item.getTitle();
            mSummary = item.getSummary();
            intent = new Intent(this, AudioPlayerService.class);
            Bundle serviceBundle = new Bundle();
            serviceBundle.putParcelable(AppConstants.ITEM_KEY, item);
            intent.putExtra(AppConstants.BUNDLE_KEY, serviceBundle);
            Util.startForegroundService(this, intent);
            mPlayerView.setUseController(true);
            mPlayerView.showController();
            mPlayerView.setControllerAutoShow(true);
            mPlayerView.setControllerHideOnTouch(false);
        }
    }

    private void initializePlayer() {
        if (mBound) {
            SimpleExoPlayer player = mService.getplayerInstance();
            mPlayerView.setPlayer(player);
        }
    }

    @Override
    public void onStart() {
        super.onStart();
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        initializePlayer();
        setUI();
    }

    private void setUI() {
        mTvTitle.setText(mTitle);
        mTvSummary.setText(mSummary);
        GlideApp.with(this)
                .load(mImage)
                .placeholder(R.color.colorPrimary)
                .diskCacheStrategy(DiskCacheStrategy.ALL)
                .into(mIvThumb);
    }

    @Override
    protected void onStop() {
        unbindService(mConnection);
        mBound = false;
        super.onStop();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.player_menu, menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.share_podcast:
                Intent shareIntent = new Intent();
                shareIntent.setAction(Intent.ACTION_SEND);
                shareIntent.putExtra(Intent.EXTRA_SUBJECT, mTitle);
                shareIntent.putExtra(Intent.EXTRA_TEXT, mTitle + "\n\n" + shareableLink);
                shareIntent.setType("text/plain");
                startActivity(Intent.createChooser(shareIntent, getString(R.string.share_text)));
                return true;
            case R.id.download_podcast:
                Uri uri = Uri.parse(mUrl);
                ProgressiveDownloadAction progressiveDownloadAction
                        = new ProgressiveDownloadAction(uri, false, null, null);
                AudioDownloadService.startWithAction(PlayerActivity.this, AudioDownloadService.class,
                        progressiveDownloadAction, false);
                return true;
            case android.R.id.home:
                onBackPressed();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    @Override
    public void onToolBarSetUp(Toolbar toolbar, ActionBar actionBar) {
        TextView tvHeader = toolbar.findViewById(R.id.tvClassName);
        tvHeader.setText(R.string.app_name);
        actionBar.setHomeAsUpIndicator(R.drawable.ic_arrow_back_black_24dp);
    }
}

AudioPlayerService

    public class AudioPlayerService extends Service {

    private final IBinder mBinder = new LocalBinder();
    private SimpleExoPlayer player;
    private Item item;
    private PlayerNotificationManager playerNotificationManager;

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

    @Override
    public void onDestroy() {
        releasePlayer();
        super.onDestroy();
    }

    private void releasePlayer() {
        if (player != null) {
            playerNotificationManager.setPlayer(null);
            player.release();
            player = null;
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    public SimpleExoPlayer getplayerInstance() {
        if (player == null) {
            startPlayer();
        }
        return player;
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //releasePlayer();
        Bundle b = intent.getBundleExtra(AppConstants.BUNDLE_KEY);
        if (b != null) {
            item = b.getParcelable(AppConstants.ITEM_KEY);
        }
        if (player == null) {
            startPlayer();
        }
        return START_STICKY;
    }

    private void startPlayer() {
        final Context context = this;
        Uri uri = Uri.parse(item.getUrl());
        player = ExoPlayerFactory.newSimpleInstance(context, new DefaultTrackSelector());
        DefaultDataSourceFactory dataSourceFactory = new DefaultDataSourceFactory(context,
                Util.getUserAgent(context, getString(R.string.app_name)));
        CacheDataSourceFactory cacheDataSourceFactory = new CacheDataSourceFactory(
                DownloadUtil.getCache(context),
                dataSourceFactory,
                CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR);
        MediaSource mediaSource = new ExtractorMediaSource.Factory(cacheDataSourceFactory)
                .createMediaSource(uri);
        player.prepare(mediaSource);
        player.setPlayWhenReady(true);
        playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(context, AppConstants.PLAYBACK_CHANNEL_ID,
                R.string.playback_channel_name,
                AppConstants.PLAYBACK_NOTIFICATION_ID,
                new PlayerNotificationManager.MediaDescriptionAdapter() {
                    @Override
                    public String getCurrentContentTitle(Player player) {
                        return item.getTitle();
                    }

                    @Nullable
                    @Override
                    public PendingIntent createCurrentContentIntent(Player player) {
                        Intent intent = new Intent(context, PlayerActivity.class);
                        Bundle serviceBundle = new Bundle();
                        serviceBundle.putParcelable(AppConstants.ITEM_KEY, item);
                        intent.putExtra(AppConstants.BUNDLE_KEY, serviceBundle);
                        return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
                    }

                    @Nullable
                    @Override
                    public String getCurrentContentText(Player player) {
                        return item.getSummary();
                    }

                    @Nullable
                    @Override
                    public Bitmap getCurrentLargeIcon(Player player, PlayerNotificationManager.BitmapCallback callback) {
                        return null;
                    }
                }
        );
        playerNotificationManager.setNotificationListener(new PlayerNotificationManager.NotificationListener() {
            @Override
            public void onNotificationStarted(int notificationId, Notification notification) {
                startForeground(notificationId, notification);
            }

            @Override
            public void onNotificationCancelled(int notificationId) {
                stopSelf();
            }
        });
        playerNotificationManager.setPlayer(player);
    }

    public class LocalBinder extends Binder {
        public AudioPlayerService getService() {
            return AudioPlayerService.this;
        }
    }
}