You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

ExoPlayer后台播放实现及直播M3U8流无法播放问题咨询

解决ExoPlayer应用的两个核心问题:后台播放与直播M3U8流播放

Let's tackle your two issues with practical fixes based on your code:


问题1:关闭应用/Activity后无法后台播放视频

代码里的核心问题

你的当前实现把ExoPlayer直接绑定在MainActivity里,当Activity进入后台(onPause)或者被销毁时,播放器会被暂停甚至释放,而且Android系统会优先回收后台Activity的资源,导致播放中断。另外,你的代码里还有一个致命的空指针问题:

// 错误:player还没初始化就调用setMediaItem,会抛出NullPointerException
player.setMediaItem(mediaItem);
player = new ExoPlayer.Builder(this).build();

解决方案:用Foreground Service托管播放器

要实现后台播放,必须把ExoPlayer移到Foreground Service中(前台服务会被系统优先保留,不会轻易被回收),Activity只负责和Service通信控制播放。

步骤1:创建Foreground Service

新建一个PlayerService.java类:

package com.live.cricketmatches;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import androidx.core.app.NotificationCompat;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.ui.PlayerNotificationManager;

public class PlayerService extends Service {
    private ExoPlayer player;
    private final IBinder binder = new LocalBinder();
    private PlayerNotificationManager notificationManager;

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

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

    @Override
    public void onCreate() {
        super.onCreate();
        // 初始化ExoPlayer
        player = new ExoPlayer.Builder(this).build();
        
        // 创建通知渠道(Android O及以上需要)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(
                "player_channel",
                "Video Player",
                NotificationManager.IMPORTANCE_LOW
            );
            NotificationManager notificationManager = getSystemService(NotificationManager.class);
            notificationManager.createNotificationChannel(channel);
        }

        // 设置前台通知,让服务在前台运行
        notificationManager = PlayerNotificationManager.createWithNotificationChannel(
            this,
            "player_channel",
            R.string.app_name,
            R.string.app_name,
            new PlayerNotificationManager.MediaDescriptionAdapter() {
                @Override
                public CharSequence getCurrentContentTitle(ExoPlayer player) {
                    return "Live Video";
                }

                @Override
                public NotificationCompat.MediaStyle getMediaStyle(NotificationCompat.Builder builder, ExoPlayer player) {
                    return null;
                }

                @Override
                public CharSequence getCurrentContentText(ExoPlayer player) {
                    return "Playing in background";
                }

                @Override
                public Bitmap getCurrentLargeIcon(ExoPlayer player, PlayerNotificationManager.BitmapCallback callback) {
                    return null;
                }
            }
        );
        notificationManager.setPlayer(player);
    }

    public ExoPlayer getPlayer() {
        return player;
    }

    public void setMediaItem(String url) {
        MediaItem mediaItem = MediaItem.fromUri(url);
        player.setMediaItem(mediaItem);
        player.prepare();
        player.setPlayWhenReady(true);
    }

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

步骤2:修改MainActivity,绑定Service并控制播放

更新你的MainActivity5.java,修复空指针问题,并绑定Service:

// 省略其他导入...
import android.content.ComponentName;
import android.content.ServiceConnection;
import android.os.IBinder;

public class MainActivity5 extends AppCompatActivity {
    private ExoPlayer player;
    private PlayerService playerService;
    private boolean isServiceBound = false;

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            PlayerService.LocalBinder binder = (PlayerService.LocalBinder) service;
            playerService = binder.getService();
            player = playerService.getPlayer();
            playerView.setPlayer(player);
            // 设置播放源(这里可以替换成你的直播URL)
            playerService.setMediaItem("https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8");
            isServiceBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            isServiceBound = false;
            player = null;
            playerService = null;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main5);
        PlayerView playerView = findViewById(R.id.video_view);
        progressBar= findViewById(R.id.exo_progress);
        btFullScreen= playerView.findViewById(R.id.exo_fullscreen_button);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);

        playerView.setKeepScreenOn(true);

        // 绑定Service
        Intent serviceIntent = new Intent(this, PlayerService.class);
        bindService(serviceIntent, serviceConnection, BIND_AUTO_CREATE);

        // 剩下的全屏按钮、质量选择等逻辑保留...
    }

    // 移除原来的onPause和onRestart里的播放控制,因为播放器在Service里
    @Override
    protected void onPause() {
        super.onPause();
        // 不需要暂停播放,让Service继续播放
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        // 重新绑定播放器到View
        if (isServiceBound && player != null) {
            PlayerView playerView = findViewById(R.id.video_view);
            playerView.setPlayer(player);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (isServiceBound) {
            unbindService(serviceConnection);
            isServiceBound = false;
        }
    }

    // 剩下的代码(菜单、播放器Listener等)保留...
}

步骤3:在AndroidManifest.xml中注册Service

添加以下代码到AndroidManifest.xml

<service
    android:name=".PlayerService"
    android:foregroundServiceType="mediaPlayback" />

问题2:直播M3U8流无法播放

可能的原因及解决方案

1. 缺少HLS扩展库

ExoPlayer的核心库不包含所有HLS特性,尤其是加密直播流或者特殊编码的流,需要添加HLS扩展依赖。
在你的build.gradle(Module级别)中添加:

implementation "com.google.android.exoplayer:exoplayer-hls:2.X.X"
// 替换2.X.X为你当前使用的ExoPlayer版本,要和核心库版本一致

2. 不正确的MediaSource配置

你的代码里直接用MediaItem.fromUri,对于直播流,建议显式使用HlsMediaSource来配置直播相关参数:
修改PlayerService里的setMediaItem方法:

import com.google.android.exoplayer2.source.hls.HlsMediaSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;

public void setMediaItem(String url) {
    DefaultHttpDataSource.Factory dataSourceFactory = new DefaultHttpDataSource.Factory()
            .setUserAgent("YourAppName/1.0"); // 设置User-Agent,有些直播服务器会验证这个
    
    HlsMediaSource hlsMediaSource = new HlsMediaSource.Factory(dataSourceFactory)
            .setAllowChunklessPreparation(true) // 针对直播流优化,减少准备时间
            .createMediaSource(MediaItem.fromUri(url));
    
    player.setMediaSource(hlsMediaSource);
    player.prepare();
    player.setPlayWhenReady(true);
}

3. 网络或服务器限制

  • 有些直播服务器会限制请求的User-Agent,确保你设置了合理的User-Agent(如上代码所示)
  • 检查是否需要添加网络权限(你应该已经有<uses-permission android:name="android.permission.INTERNET" />
  • 测试直播URL是否可以在其他播放器(比如VLC)中正常播放,排除服务器本身的问题

4. 调试播放错误

在你的播放器Listener中,添加错误日志,方便定位问题:

@Override
public void onPlayerError(PlaybackException error) {
    super.onPlayerError(error);
    Log.e("ExoPlayerError", "Error code: " + error.errorCodeName + ", message: " + error.getMessage());
    Toast.makeText(MainActivity5.this, "播放失败:" + error.getMessage(), Toast.LENGTH_LONG).show();
}

内容的提问来源于stack exchange,提问作者Farhana Nazir

火山引擎 最新活动