ExoPlayer简单使用

原创
2019/04/23 17:34
阅读数 6.2K

一、介绍

ExoPlayer是google开源的应用级媒体播放器项目,该开源项目包含ExoPlayer库和演示demo,github地址:https://github.com/google/ExoPlayer

二、概述

ExoPlayer库的核心是ExoPlayer接口。ExoPlayer公开了传统的高水平媒体播放器的功能,例如媒体缓冲,播放,暂停和快进功能。ExoPlayer没有直接实现媒体文件的加载和渲染,而是把这些工作委托给了在创建播放器或者播放器准备好播放的时候注入的组件。所有ExoPlayer实现的通用组件是:

  • MediaSource:媒体资源,用于定义要播放的媒体,加载媒体,以及从哪里加载媒体。简单的说,MediaSource就是代表我们要播放的媒体文件,可以是本地资源,可以是网络资源。MediaSource在播放开始的时候,通过ExoPlayer.prepare方法注入。
  • Renderer:渲染器,用于渲染媒体文件。当创建播放器的时候,Renderers被注入。
  • TrackSelector:轨道选择器,用于选择MediaSource提供的轨道(tracks),供每个可用的渲染器使用。
  • LoadControl:用于控制MediaSource何时缓冲更多的媒体资源以及缓冲多少媒体资源。LoadControl在创建播放器的时候被注入。

三、使用

1、添加依赖

implementation 'com.google.android.exoplayer:exoplayer:2.X.X'

此种方式依赖了整个ExoPlayer库。我们也可以只依赖自己真正需要的库。例如果你要播放普通的媒体资源,你可以只依赖Core,UI这两个个库。

implementation 'com.google.android.exoplayer:exoplayer-core:2.X.X'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.X.X'

整个ExoPlayer库包括5个子库,依赖了整个ExoPlayer库和依赖5个子库是等效的。

  • exoplayer-core:核心功能 (必要)
  • exoplayer-dash:支持DASH内容
  • exoplayer-hls:支持HLS内容
  • exoplayer-smoothstreaming:支持SmoothStreaming内容
  • exoplayer-ui:用于ExoPlayer的UI组件和相关的资源。

  2、在布局文件中加入PlayerView

<com.google.android.exoplayer2.ui.PlayerView
    android:id="@+id/exo_play_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
private PlayerView mPlayerView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    [...]
    mPlayerView = findViewById(R.id.exo_play_view);
}

3、 创建播放器SimpleExoPlayer实例

SimpleExoPlayer是ExoPlayer接口的一个默认的通用实现。

// 得到默认合适的带宽
BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
// 创建跟踪的工厂
TrackSelection.Factory factory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
// 创建跟踪器
DefaultTrackSelector trackSelection = new DefaultTrackSelector(factory);

// 创建播放器
SimpleExoPlayer exoPlayer = ExoPlayerFactory.newSimpleInstance(context, trackSelection);

// 第二种方式 传入了默认的渲染工厂(DefaultRenderersFactory),默认的轨道选择器(DefaultTrackSelector)和默认的加载控制器(DefaultLoadControl),然后把返回的播放器实例
//SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance(
//         new DefaultRenderersFactory(context),
//         new DefaultTrackSelector(), 
//         new DefaultLoadControl());

4、准备并开始播放

// 生成数据媒体实例,通过该实例加载媒体数据
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(context, Util.getUserAgent(context, "exoplayerdemo"));
// 创建资源
Uri uri = Uri.parse(url);
MediaSource mediaSources = new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
// 将播放器附加到view
mPlayerView.setPlayer(exoPlayer);
// 准备播放
exoPlayer.prepare(mediaSource);
// 准备好了之后自动播放,如果已经准备好了,调用该方法实现暂停、开始功能
exoPlayer.setPlayWhenReady(true);

5、释放播放器

if (null != exoPlayer) {
    exoPlayer.stop();
    exoPlayer.release();
    exoPlayer = null;
}

6、组合媒体资源

ExoPlayer库提供了ConcatenatingMediaSource和DynamicConcatenatingMediaSource可以用来无缝的合并播放多个媒体资源。

使用ConcatenatingMediaSource
下面的方法构建了组合的媒体文件。

/**
 * list是资源地址集合,可以是音频、视频等
 */
private MediaSource getMediaSource(ArrayList<String> list) {
    DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(context, Util.getUserAgent(context, "exoplayerdemo"));
    // 将URL组装成资源类
    MediaSource[] mediaSources = new MediaSource[list.size()];
    for (int i = 0; i < list.size(); i++) {
        Uri uri = Uri.parse(list.get(i));
        mediaSources[i] = new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
    }
    MediaSource mediaSource = mediaSources.length == 1 ? mediaSources[0] : new ConcatenatingMediaSource(mediaSources);
    return mediaSource;
}

7、循环播放

ExoPlayer库提供了LoopingMediaSource实现循环播放。

// 单个资源循环播放 可指定loopCount循环次数
MediaSource source = new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
MediaSource mediaSource = new LoopingMediaSource(source, loopCount);

// 多个资源循环播放
MediaSource source = new ConcatenatingMediaSource(mediaSources);
MediaSource mediaSource = new LoopingMediaSource(source, loopCount);

8、监听ExoPlayer事件

// 添加事件监听
exoPlayer.addListener(listener);
// 移除事件监听
exoPlayer.removeListener(listener);

private Player.DefaultEventListener listener = new Player.DefaultEventListener() {
    @Override
    public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
        // 视频播放状态
        L.d("playbackState = " + playbackState + " playWhenReady = " + playWhenReady);
        switch (playbackState){
            case Player.STATE_IDLE:
                // 空闲
                break;
            case Player.STATE_BUFFERING:
                // 缓冲中
                break;
            case Player.STATE_READY:
                // 准备好
                break;
            case Player.STATE_ENDED:
                // 结束
                break;
            default:
                break;
        }
    }

    @Override
    public void onPlayerError(ExoPlaybackException error) {
        // 报错
        switch (error.type){
            case ExoPlaybackException.TYPE_SOURCE:
                // 加载资源时出错
                break;
            case ExoPlaybackException.TYPE_RENDERER:
                // 渲染时出错
                break;
            case ExoPlaybackException.TYPE_UNEXPECTED:
                // 意外的错误
                break;
        }
    }
};

9、自定义界面

如果不设置的话,ExoPlayer 默认使用的播放控制界面是PlayerControlView如果完全不想使用这个控制界面,可以在布局文件里面修改

<com.google.android.exoplayer2.ui.PlayerView
   [...]
   app:use_controller="false"/>

这样控制界面就不显示了。

自定义PlayerControlView的行为

<com.google.android.exoplayer2.ui.PlayerView
   android:id="@+id/video_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   app:show_timeout="10000"
   app:fastforward_increment="30000"
   app:rewind_increment="30000"/>

上面的例子中,快进和快退都改成了30秒。控制界面自动消失时间是10秒。

自定义PlayerControlView界面的外观

你可以自定义控制界面,然后在布局文件里更改属性 controller_layout_id。

<com.google.android.exoplayer2.ui.PlayerView  
   android:id="@+id/video_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   app:controller_layout_id="@layout/custom_playback_control"/>
PlayerControlView通过id来识别它使用的所有UI元素。当你自定义布局文件的时候,必须保持标准元素的id,例如@id/exo_play 或 @id/exo_pause,以便让PlayerControlView有机会找到它们。

默认的PlayerControlView的控制界面是R.layout.exo_playback_control_view.xml。你也可以直接从ExoPlayer库中复制到app的res目录下面,然后做相应的更改即可。

四、结尾

基本的播放差不多已经完成了。ExoPlayer还有很多高级功能,例如

1、资源剪切 ClippingMediaSource
2、倍速播放
3、字幕
4、播放流媒体
等等等等

还没有研究,有兴趣的小伙伴可以去看看。

在来一个完整的代码

package com.example.myapplication;

import android.content.Context;
import android.net.Uri;
import android.util.Log;
import android.widget.Toast;

import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.LoopingMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.util.Util;

import java.util.ArrayList;

public class ExoPlayer extends Player.DefaultEventListener {

    private Context context;
    private SimpleExoPlayer exoPlayer;
    private DataSource.Factory dataSourceFactory;
    private ArrayList<String> urlList;
    private PlayerView playerView;
    private boolean playWhenReady = true;
    private boolean loop = false;
    private int loopCount;

    public static ExoPlayerBuilder with(Context context) {
        if (context == null) {
            throw new IllegalStateException("Context is null.");
        } else {
            return new ExoPlayerBuilder(context);
        }
    }

    static ExoPlayer create(ExoPlayerBuilder builder) {
        return new ExoPlayer(builder.context, builder.urlList, builder.loop, builder.loopCount, builder.playWhenReady, builder.playerView);
    }

    public ExoPlayer(Context context, ArrayList<String> list, boolean loop, int loopCount, boolean playWhenReady, PlayerView playerView) {
        this.context = context;
        this.urlList = list;
        this.loop = loop;
        this.loopCount = loopCount;
        this.playWhenReady = playWhenReady;
        this.playerView = playerView;
    }

    private MediaSource getMediaSource() {
        // 将URL组装成资源类
        MediaSource[] mediaSources = new MediaSource[urlList.size()];
        for (int i = 0; i < urlList.size(); i++) {
            Uri uri = Uri.parse(urlList.get(i));
            mediaSources[i] = new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
        }
        MediaSource mediaSource = mediaSources.length == 1 ? mediaSources[0] : new ConcatenatingMediaSource(mediaSources);
        if (loop) {
            mediaSource = new LoopingMediaSource(mediaSource, loopCount);
        }
        return mediaSource;
    }

    /**
     * 播放列表
     */
    public void play() {
        // 创建默认的 TrackSelector
        // 得到默认合适的带宽
        BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
        // 创建跟踪的工厂
        TrackSelection.Factory factory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
        // 创建跟踪器
        DefaultTrackSelector trackSelection = new DefaultTrackSelector(factory);

        // 生成数据媒体实例,通过该实例加载媒体数据
        dataSourceFactory = new DefaultDataSourceFactory(context, Util.getUserAgent(context, "exoplayerdemo"));

        // 将URL组装成资源类
        MediaSource mediaSource = getMediaSource();

        // 创建播放器
        exoPlayer = ExoPlayerFactory.newSimpleInstance(context, trackSelection);
        // 将播放器附加到view
        if (null != playerView) {
            playerView.setPlayer(exoPlayer);
        }
        // 添加事件监听
        exoPlayer.addListener(this);
        exoPlayer.prepare(mediaSource);
        // 准备好了之后自动播放,如果已经准备好了,调用该方法实现暂停、开始功能
        exoPlayer.setPlayWhenReady(playWhenReady);
    }

    /**
     * 暂停
     */
    public void pause() {
        if (null != exoPlayer) {
            exoPlayer.setPlayWhenReady(false);
        }
    }

    /**
     * 继续
     */
    public void resume() {
        if (null != exoPlayer) {
            exoPlayer.setPlayWhenReady(true);
        }
    }

    /**
     * 下一首
     */
    public void next() {
        if (null != exoPlayer) {
            if (exoPlayer.getCurrentTimeline().isEmpty()) {
                return;
            }
            int nextWindowIndex = exoPlayer.getNextWindowIndex();
            if (nextWindowIndex != C.INDEX_UNSET) {
                exoPlayer.seekTo(nextWindowIndex, C.TIME_UNSET);
            } else {
                Log.d("info", "已经是最后一个了");
            }
        }
    }

    /**
     * 上一首
     */
    public void previous() {
        if (null != exoPlayer) {
            if (exoPlayer.getCurrentTimeline().isEmpty()) {
                return;
            }
            int previousWindowIndex = exoPlayer.getPreviousWindowIndex();
            if (previousWindowIndex != C.INDEX_UNSET) {
                exoPlayer.seekTo(previousWindowIndex, C.TIME_UNSET);
            } else {
                Log.d("info", "已经是第一个了");
            }
        }
    }

    /**
     * 停止
     */
    public void stop() {
        if (null != exoPlayer) {
            exoPlayer.stop();
        }
    }

    /**
     * 释放资源
     */
    public void release() {
        if (null != exoPlayer) {
            exoPlayer.removeListener(this);
            exoPlayer.stop();
            exoPlayer.release();
            exoPlayer = null;
        }
    }

    /**
     * 是否在播放中
     *
     * @return
     */
    public boolean isPlaying() {
        return exoPlayer != null
                && exoPlayer.getPlaybackState() != Player.STATE_ENDED
                && exoPlayer.getPlaybackState() != Player.STATE_IDLE
                && exoPlayer.getPlayWhenReady();
    }

    /**
     * 获取总时长
     *
     * @return 毫秒
     */
    public long getDuration() {
        long duration = 0;
        if (null != exoPlayer) {
            duration = exoPlayer.getDuration();
            // 获取时间失败
            if (duration == C.TIME_UNSET) {
                duration = 0;
            }
        }
        return duration;
    }

    /**
     * 获取当前播放进度
     *
     * @return 毫秒
     */
    public long getCurrentPosition() {
        long position = 0;
        if (null != exoPlayer) {
            position = exoPlayer.getCurrentPosition();
        }
        return position;
    }

    @Override
    public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
        // 视频播放状态
        Log.d("", "playbackState = " + playbackState + " playWhenReady = " + playWhenReady);
        switch (playbackState) {
            case Player.STATE_IDLE:
                // 空闲
                break;
            case Player.STATE_BUFFERING:
                // 缓冲中
                break;
            case Player.STATE_READY:
                // 准备好
                break;
            case Player.STATE_ENDED:
                // 结束
                break;
            default:
                break;
        }
    }

    @Override
    public void onPlayerError(ExoPlaybackException error) {
        // 报错
        Log.e("info", "error.type = " + error.type);
        switch (error.type) {
            case ExoPlaybackException.TYPE_SOURCE:
                // 加载资源时出错
                break;
            case ExoPlaybackException.TYPE_RENDERER:
                // 渲染时出错
                break;
            case ExoPlaybackException.TYPE_UNEXPECTED:
                // 意外的错误
                break;
        }
    }
}

package com.example.myapplication;

import android.content.Context;

import com.google.android.exoplayer2.ui.PlayerView;

import java.util.ArrayList;

public class ExoPlayerBuilder {

    ArrayList<String> urlList = new ArrayList<>();
    Context context;
    PlayerView playerView;
    boolean playWhenReady = true;
    boolean loop = false;
    int loopCount;

    public ExoPlayerBuilder(Context context){
        this.context = context;
    }

    public ExoPlayerBuilder into(PlayerView playerView){
        this.playerView = playerView;
        return this;
    }

    public ExoPlayerBuilder loop(boolean loop){
        this.loop = loop;
        return this;
    }

    public ExoPlayerBuilder loopCount(int loopCount){
        this.loopCount = loopCount;
        return this;
    }

    public ExoPlayerBuilder playWhenReady(boolean play){
        this.playWhenReady = play;
        return this;
    }

    public ExoPlayerBuilder addUrl(String url){
        if (urlList == null){
            urlList = new ArrayList<>();
        }
        urlList.add(url);
        return this;
    }

    public ExoPlayer build(){
        validate();
        return ExoPlayer.create(this);
    }

    private void validate() {
        if (this.urlList.size() == 0){
            throw new IllegalStateException("Play url is empty..");
        }
    }
}

 

展开阅读全文
打赏
0
1 收藏
分享
加载中
更多评论
打赏
0 评论
1 收藏
0
分享
返回顶部
顶部