Jetpack - Media3(ExoPlayer 播放器控制)

官方页面

一、概念

1.1 架构

Player  播放器ExoPlayer 是具体实现。
MediaSession 媒体会话协调 MediaController 和 Player。
MediaController 媒体控制器用于跨组件或外部远程控制媒体播放(耳机、手表、电视、汽车、谷歌助理)。
MediaSessionService用于支持跨组件播放的Service。(ExoPlayer支持处于后台时播放)

1.2 协议支持

DASHHLSSmoothStreaming
全称Dynamic Adaptive Streaming over HTTPHTTP Live StreamingIIS Smooth Streaming
主导方MPEG(国际标准)苹果微软
清单文件格式基于XML的 .mpd基于文本的 .m3u8基于XML的 .ismc
现状业界事实标准之一,更开放、灵活业界事实标准之一,兼容性极佳遗产协议,仍在某些系统使用
RTSP/RTPHLS / DASH / Smooth Streaming
协议控制协议 + 数据传输协议基于HTTP的应用协议
典型应用IP摄像头、视频会议、专网监控互联网视频网站(Netflix/YouTube)、公共直播
MIDI音频文件播放(MP3, WAV等)
文件内容指令集(乐谱)声音波形的编码(录音)
最终音质取决于播放设备的音源取决于文件本身的编码和比特率

二、简单使用

2.1 添加依赖

最新版本

[versions]
media3 = "1.9.0"

[libraries]
media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3" }
media3-datasource-okhttp = { module = "androidx.media3:media3-datasource-okhttp", version.ref = "media3" }
media3-ui = { module = "androidx.media3:media3-ui", version.ref = "media3" }
//可选,使用 MediaSession
media3-session = { module = "androidx.media3:media3-session", version.ref = "media3" }

可选协议支持

//DASH
media3-dash = { module = "androidx.media3:media3-exoplayer-dash", version.ref = "media3" }
//HLS
media3-hls = { module = "androidx.media3:media3-exoplayer-hls", version.ref = "media3" }
//SmoothStreaming
media3-smoothstreaming = { module = "androidx.media3:media3-exoplayer-smoothstreaming", version.ref = "media3" }
//RTSP
media3-rtsp = { module = "androidx.media3:media3-exoplayer-rtsp", version.ref = "media3" }
//MIDI
media3-midi = { module = "androidx.media3:media3-exoplayer-midi", version.ref = "media3" }
//For ad insertion using the Interactive Media Ads SDK with ExoPlayer
media3-ima = { module = "androidx.media3:media3-exoplayer-ima", version.ref = "media3" }

2.2 添加控件 PlayerView

可用于视频、图片和音频播放。在播放视频时,它会呈现视频和字幕;在播放图片时,它会呈现位图;并且可以显示音频文件中作为元数据包含的封面图片。

<androidx.media3.ui.PlayerView
    android:id="@+id/playerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:keepScreenOn="true"
    />
Activity {
    override fun onStart() {
        super.onResume()
        playerView.onResume()
    }
    override fun onStop() {
        super.onStop()
        playerView.onPause()
    }
}

2.3 创建播放器 ExoPlayer

每次快进快退的时间

public Builder setSeekBackIncrementMs(@IntRange(from = 1) long seekBackIncrementMs)

public Builder setSeekForwardIncrementMs(@IntRange(from = 1) long seekForwardIncrementMs)

val player = ExoPlayer.Builder(APP.context)
    .setSeekBackIncrementMs(10000)    //每次快退的时间(10秒)
    .setSeekForwardIncrementMs(10000)    //每次快进的时间(10秒)
    .build()
playerView.player = player    //将播放器和控件绑定
player.prepare()
playWhenReady = true

2.4 添加媒体项 MediaItem

通过 Uri 、Bundle 指定播放文件。播放期间可以更新播放列表,无需再次准备播放器。

public static MediaItem fromUri(String uri)

public static MediaItem fromUri(Uri uri)

public static MediaItem fromBundle(Bundle bundle)

//创建播放内容
val mediaItem = MediaItem.fromUri("")
//将播放内容添加到播放器中
player.setMediaItem(mediaItem)

2.6 控制播放器

初始化

void prepare();

将使播放器脱离空闲状态,开始加载媒体内容并获取播放所需的系统资源。

播放void play();
暂停void pause();
释放播放器

void release();

释放播放器实例。当不再需要使用播放器时,必须调用此方法。调用此方法后,严禁继续使用该播放器实例。

自动播放

boolean getPlayWhenReady();

void setPlayWhenReady(boolean playWhenReady);

播放器就绪状态时是否自动播放。

播放速度

void setPlaybackSpeed(@FloatRange(from = 0, fromInclusive = false) float speed);

调整速率(不改变音调),参数 speed 必须大于 0,1.0 为正常速率,2.0 为两倍速,0.5 为半速。

状态判断

boolean isPlaying();

是否正在播放中。

boolean isLoading();

是否正在加载资源。

跳转

void seekToPreviousMediaItem();

void seekToNextMediaItem();

跳转到上一个或下一个媒体项。

boolean hasPreviousMediaItem();

boolean hasNextMediaItem();

是否有上一个或下一个媒体项。

void seekTo(int mediaItemIndex, long positionMs);

跳转至指定媒体项中由毫秒数定义的播放位置。

进度条

void seekBack();

void seekForward();

将当前媒体项的播放位置向前或向后跳到创建播放器时,通过 setSeekBackIncrement()或 setSeekForwardIncrement() 所定义的毫秒值。

void seekTo(long positionMs);

跳转至当前媒体项中指定的毫秒数位置。

音量增益

float getVolume();
void setVolume(@FloatRange(from = 0, to = 1.0) float volume);

设置音频输出增益。有效取值范围为 0(静音)至 1(单位增益,保持原信号)的闭区间。

三、监听事件

可以通过 player.addListener() 或挂起函数 player.listen{} 来监听。Player.Listener 没有必须实现的方法,只需要实现感兴趣的。

状态更改

default void onPlaybackStateChanged(@State int playbackState) {}

分别有:空闲 Player.STATE_IDLE、缓冲中 Player.STATE_BUFFERING、就绪 Player.STATE_READY、播放完 Player.STATE_ENDED。

也可以直接通过 player.playbackState 获取。

是否在播放

default void onIsPlayingChanged(boolean isPlaying) {}

也可以直接用通过 player.isPlaying 获取。

播放错误default void onPlayerError(PlaybackException error) {}
player.addListener(object : Player.Listener {
    //TODO
})

player.listen { event ->
    //TODO
}

3.1 状态更改 onPlaybackStateChanged()

override fun onPlaybackStateChanged(playbackState: Int) {
    when(playbackState) {
        //空闲
        Player.STATE_IDLE -> {}
        //缓冲中
        Player.STATE_BUFFERING -> {}
        //就绪
        Player.STATE_READY -> {}
        //播放完
        Player.STATE_ENDED -> {}
    }
}

3.2 是否在播放 onIsPlayingChanged()

override fun onIsPlayingChanged(isPlaying: Boolean) {
    if (isPlaying) {} else {}
}

3.3 播放错误 onPlayerError()

override fun onPlayerError(error: PlaybackException) {
    when (error.cause) {
        //http错误
        is HttpDataSource.HttpDataSourceException -> {}
        else -> {}
    }
    when (error.errorCode) {
        //网络错误
        PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED -> {}
        //解码错误
        PlaybackException.ERROR_CODE_DECODING_FAILED -> {}
        else -> {}
    }
}

3.4 播放列表/媒体源变化 onTimelineChanged()

override fun onTimelineChanged(timeline: Timeline, reason: Int) {
    when(reason) {
        //播放列表发生变化时
        Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED -> {}
        //媒体源更新而发生变化
        Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE -> {}
    }
}

3.5 切歌 onMediaItemTransition()

override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {}

3.6 监听播放进度

如果需要设置进度条,Listener 中没有对应的回调用来持续监听播放进度,需要手动实现(按一定时间间隔去查询)。

if (player.isPlaying) {
    //每秒更新一次
    repeat(1000) {
        val currentPosition = player.currentPosition
        val duration = player.duration
        seekBar.progress = if (duration > 0) {
            (currentPos * 100 / duration).toInt()
        } else 0
        currentText = formatTime(currentPosition)
        durationText = formatTime(duration)
    }
}

四、媒体项 MediaItem

通过 Builder 模式构建实例。MediaItem 会由 MediaSource.Factory 转换为可播放的 MediaSource。如果未进行自定义配置,此转换将由 DefaultMediaSourceFactory 执行,它能够构建与媒体内容的属性对应的复杂媒体源。

设置Uri

public Builder setUri(@Nullable Uri uri)

public Builder setUri(@Nullable String uri)

设置媒体资源。

设置ID

public Builder setMediaId(String mediaId)

用于识别媒体项。

设置Tag

public Builder setTag(@Nullable Object tag)

为媒体项设置一个自定义代码,可以是任意对象,一般用于元数据。

获取元数据

public final MediaMetadata mediaMetadata;

通过属性获取媒体项的元数据,包含了:媒体文件类型、作者、专辑、时长等。

val mediaItem = MediaItem.Builder()
    .setUri("")
    .setTag("")
    .setMediaId("")
    .build()

4.1 串流

ExoPlayer 为 DASH、HLS 和 SmoothStreaming 提供自适应媒体源。如果此类自适应媒体内容的 URI 以标准文件扩展名结尾,系统会自动创建相应的媒体源。如果 URI 具有非标准扩展名或没有扩展名,则可以明确设置 MIME 类型以指明媒体内容的类型。对于渐进式媒体串流,不需要 MIME 类型。

便捷创建串流媒体项

public static MediaItem fromUri(String uri)

public static MediaItem fromUri(Uri uri)

快捷构建仅包含串流 URI 的媒体项。

设置自适应媒体的类型

public Builder setMimeType(@Nullable String mimeType)

val mediaItem = MediaItem.fromUri("")
    .setMimeType(MimeTypes.APPLICATION_M3U8)

4.2 图像

播放图片,媒体项中必须包含时长,以指定图片在播放期间应显示的时长。

图片播放时常public Builder setImageDurationMs(long imageDurationMs)
val mediaItem = MediaItem.Builder()
    .setUri(imageUri)
    .setImageDurationMs(3000)
    .build()

4.3 受保护的内容

对于受保护的内容,应设置媒体内容的 DRM 属性。UUID 是必需的,所有其他属性都是可选的。

val mediaItem = MediaItem.Builder()
    .setUri("")
    .setDrmConfiguration(
        MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
            .setLicenseUri("")
            .setMultiSession(true)
            .setLicenseRequestHeaders(mapOf("" to ""))
            .build()
    ).build()

五、播放列表

播放列表中的项之间可以无缝切换,无格式相同要求,甚至无媒体类型相同要求,也就是播放列表可以同时包含图片、视频、音频。

5.1 增删改查

清空并添加

void setMediaItem(MediaItem mediaItem);

void setMediaItems(List<MediaItem> mediaItems);

播放位置重置为起始点。

void setMediaItem(MediaItem mediaItem, boolean resetPosition);

void setMediaItems(List<MediaItem> mediaItems, boolean resetPosition);

参数resetPosition设为flase不会重置播放位置。

void setMediaItem(MediaItem mediaItem, long startPositionMs);

void setMediaItems(List<MediaItem> mediaItems, int startIndex, long startPositionMs);

参数startPositionMs以毫秒为单位的播放起始点。参数startIndex起始索引。

添加

void addMediaItem(MediaItem mediaItem);

void addMediaItems(List<MediaItem> mediaItems);

添加到列表尾部。

void addMediaItem(int index, MediaItem mediaItem);

void addMediaItems(int index, List<MediaItem> mediaItems);

指定索引处插入。

移动

void moveMediaItem(int currentIndex, int newIndex);

将当前索引 currentIndex 的条目移动到目标索引 newIndex。currentIndex超过范围请求会被忽略,newIndex超过范围则为列表尾部。
void moveMediaItems(int fromIndex, int toIndex, int newIndex);

将起始索引 fromIndex(包含) 到目标索引 toIndex(不包含) 之间的条目,移动到目标索引newIndex。fromIndex超出范围请求会被忽略,toIndex超出范围则一直截取到尾部,newIndex超出截取后剩余的范围则为列表尾部。

替换

void replaceMediaItem(int index, MediaItem mediaItem);
void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems);

将起始索引fromIndex(包含)到目标索引toIndex(不包含) 之间的条目,替换成目标集合mediaItems。fromIndex超出范围请求会被忽略,toIndex超出范围则一直截取到尾部。

移除void removeMediaItem(int index);
void removeMediaItems(int fromIndex, int toIndex);
移除起始索引fromIndex(包含)到目标索引toIndex(不包含) 之间的条目。fromIndex超出范围请求会被忽略,toIndex超出范围则一直截取到尾部。
清空void clearMediaItems();
查找

int getCurrentMediaItemIndex();

当前播放的媒体项索引。

MediaItem getCurrentMediaItem();

当前播放的媒体项。

5.2 重复模式 RepeatMode

重复模式

int getRepeatMode();

void setRepeatMode(@RepeatMode int repeatMode);

循环关 REPEAT_MODE_OFF、单循环 REPEAT_MODE_ONE、列表循环 REPEAT_MODE_ALL。

5.3 随机播放模式 ShuffleMode

随机播放

boolean getShuffleModeEnabled();

void setShuffleModeEnabled(boolean shuffleModeEnabled);

所有内容都会播放一次。

自定义顺序

public DefaultShuffleOrder(int[] shuffledIndices, long randomSeed)

可以通过提供自定义重排顺序实现或 在 DefaultShuffleOrder 构造函数中设置自定义顺序。参数shuffledIndices作为播放顺序的乱序数组,参数randomSeed是随机数种子。

六、后台播放 MediaSession

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值