干系文章
【重生之我在学Android原生】ContentProvider(Java)
【重生之我在学Android原生】Media3
前言
内容颇多,尽量从简
ExoPlayer使用
官方文档
参考文章
实现效果
Android(java)
使用ExoPlayer播放视频,自定义ExoPlayer界面,记载播放位置(横屏竖屏切换/切换至后台等)
案例实现
创建项目
添加依赖
Sync 一下
- /// Jetpack Media3 ExoPlayer
- implementation ("androidx.media3:media3-exoplayer:1.3.1")
- implementation ("androidx.media3:media3-ui:1.3.1")
- implementation ("androidx.media3:media3-common:1.3.1")
复制代码 转到activity_main.xml
选择Player.View
- <?xml version="1.0" encoding="utf-8"?>
- <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context=".MainActivity">
- <androidx.media3.ui.PlayerView
- android:id="@+id/video_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
- </androidx.constraintlayout.widget.ConstraintLayout>
复制代码 player初始化
要在Activity的生命周期中完成player的初始化烧毁等
- package com.test.exoplayerexampleapplication;
- import androidx.annotation.OptIn;
- import androidx.appcompat.app.AppCompatActivity;
- import androidx.media3.common.util.UnstableApi;
- import androidx.media3.common.util.Util;
- import androidx.media3.ui.PlayerView;
- import android.os.Bundle;
- public class MainActivity extends AppCompatActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- }
- @OptIn(markerClass = UnstableApi.class) @Override
- protected void onStart() {
- super.onStart();
- if (Util.SDK_INT >= 24) {
- initializePlayer();
- }
- }
- @OptIn(markerClass = UnstableApi.class) @Override
- protected void onResume() {
- super.onResume();
- if (Util.SDK_INT < 24) {
- initializePlayer();
- }
- }
- private void initializePlayer() {}
- }
复制代码 定义PlayerView,ExoPlayer
- private PlayerView playerView;
- private ExoPlayer exoPlayer;
- private final Uri videoOneUri = Uri.parse("http://www.w3school.com.cn/example/html5/mov_bbb.mp4");
复制代码 在AndroidManifest.xml中定义网络权限,假如链接是Http,还需加userCleartextTraffic
- <uses-permission android:name="android.permission.INTERNET" />
复制代码- android:usesCleartextTraffic="true"
复制代码 定义initializePlayer()方法
- private void initializePlayer() {
- playerView = findViewById(R.id.video_view);
- exoPlayer = new ExoPlayer.Builder(this).build();
- playerView.setPlayer(exoPlayer);
- MediaItem mediaItem = MediaItem.fromUri(videoOneUri);
- exoPlayer.addMediaItem(mediaItem);
- exoPlayer.prepare();
- }
复制代码 运行可以播放视频
释放资源
媒体播放器是很占用资源的,所以当不再需要播放器时,要释放它,固然也要在Activity的生命周期中释放资源
在onStop和onPause适当调用
- @OptIn(markerClass = UnstableApi.class) @Override
- protected void onPause() {
- super.onPause();
- if (Util.SDK_INT < 24) {
- releasePlayer();
- }
- }
- @OptIn(markerClass = UnstableApi.class) @Override
- protected void onStop() {
- super.onStop();
- if (Util.SDK_INT >= 24) {
- releasePlayer();
- }
- }
- private void releasePlayer() {
- exoPlayer.release();
- }
复制代码 记载视频的播放状态
重新运行项目,测试切换至后台
切换至后台的生命周期,可以知道当APP放到后台,会调用player.release()方法释放
我们发现视频切换到后台,再切换回来,之前的播放到第四秒,切换回来发现,又回到第0秒。所以,现在需要记载之前播放的位置,播放的状态,播放到第几个视频了。
既然没有onDestory掉Activity
那么定义三个变量(播放位置,播放状态,第几个视频)
- private Long playbackPosition = 0L;
- private int currentMediaItemIndex = 0;
- private boolean playWhenReady = false;
复制代码
- playbackPosition = exoPlayer.getCurrentPosition();
- currentMediaItemIndex = exoPlayer.getCurrentMediaItemIndex();
- playWhenReady = exoPlayer.getPlayWhenReady();
复制代码- exoPlayer.setPlayWhenReady(playWhenReady);
- exoPlayer.seekTo(currentMediaItemIndex, playbackPosition);
复制代码 添加新的视频链接
- private final Uri videoTwoUri = Uri.parse("https://media.w3.org/2010/05/sintel/trailer.mp4");
复制代码
运行测试,播放下一个视频,并播放至一半,切换到后台,再切换回来
横竖屏切换
横竖屏切换的生命周期,Activity被Destory了
使用SharedPreference来存储键值对,参考官方文档,官方推荐用DataStore
那使用DataStore
我们现在做的事情是,使用DataStore Preference来存储视频在释放之前的播放进度,播放状态,播放到第几个视频了。
按照官网设置Preferences DataStore
引入依赖
- /// Preferences DataStore
- implementation("androidx.datastore:datastore-preferences:1.0.0")
- /// RxJava2 support
- implementation("androidx.datastore:datastore-preferences-rxjava2:1.0.0")
- /// RxJava3 support
- implementation("androidx.datastore:datastore-preferences-rxjava3:1.0.0")
复制代码 实例RxDataStore,烧毁
- private RxDataStore<Preferences> dataStore;
复制代码- if (dataStore == null) {
- dataStore = new RxPreferenceDataStoreBuilder(this, "ExoPlayerKeys").build();
- }
复制代码- @Override
- protected void onDestroy() {
- super.onDestroy();
- if (dataStore != null) {
- dataStore.dispose();
- }
- }
复制代码
定义三个Key
- Preferences.Key<Integer> currentMediaItemIndexPK;
- Preferences.Key<Integer> playbackPositionPK;
- Preferences.Key<Boolean> playWhenReadyPK;
复制代码- currentMediaItemIndexPK = PreferencesKeys.intKey("currentMediaItemIndex");
- playbackPositionPK = PreferencesKeys.intKey("playbackPosition");
- playWhenReadyPK = PreferencesKeys.booleanKey("playWhenReady");
复制代码
使用PreferencesKeys存储值
- private void saveVideoRecord() {
- dataStore.updateDataAsync(preferences -> {
- MutablePreferences mutablePreferences = preferences.toMutablePreferences();
- mutablePreferences.set(currentMediaItemIndexPK, currentMediaItemIndex);
- mutablePreferences.set(playbackPositionPK, playbackPosition.intValue());
- mutablePreferences.set(playWhenReadyPK, playWhenReady);
- return Single.just(mutablePreferences);
- });
- }
复制代码 在onCreate()中取出之前存储的值,首次运行时,是没有存值的,所以会异常NULL,所以没有值就初始化这些变量
- try {
- currentMediaItemIndex = dataStore.data().map(preferences -> preferences.get(currentMediaItemIndexPK)).blockingFirst();
- } catch (Exception e) {
- currentMediaItemIndex = 0;
- }
- try {
- playbackPosition = Long.valueOf(dataStore.data().map(preferences -> preferences.get(playbackPositionPK)).blockingFirst());
- } catch (Exception e) {
- playbackPosition = 0L;
- }
- try {
- playWhenReady = dataStore.data().map(preferences -> preferences.get(playWhenReadyPK)).blockingFirst();
- } catch (Exception e) {
- playWhenReady = false;
- }
复制代码 删除APP,重新运行项目,旋转屏幕也能记载播放状态,和播放进度,播放第几个视频
旋转后,仍然从之前播放的进度继续,保持停息。
监听事件
参考链接
- @OptIn(markerClass = UnstableApi.class)
- private void addPlayerListener() {
- listener = new Player.Listener() {
- @OptIn(markerClass = UnstableApi.class)
- @Override
- public void onPlaybackStateChanged(int playbackState) {
- Player.Listener.super.onPlaybackStateChanged(playbackState);
- String stateString = "UNKNOWN_STATE";
- if (playbackState == ExoPlayer.STATE_IDLE) {
- stateString = "EXoPlayer.STATE_IDLE";
- } else if (playbackState == ExoPlayer.STATE_BUFFERING) {
- stateString = "ExoPlayer.STATE_BUFFERING";
- } else if (playbackState == Player.STATE_READY) {
- stateString = "ExoPlayer.STATE_READY";
- } else if (playbackState == Player.STATE_ENDED) {
- stateString = "ExoPlayer.STATE_ENDED";
- }
- Log.d(TAG, "changed state to " + stateString);
- }
- };
- exoPlayer.addListener(listener);
- analyticsListener = new AnalyticsListener() {
- @Override
- public void onRenderedFirstFrame(EventTime eventTime, Object output, long renderTimeMs) {
- AnalyticsListener.super.onRenderedFirstFrame(eventTime, output, renderTimeMs);
- Log.i(TAG, "AnalyticsListener - onRenderedFirstFrame - 等待多长时间才能在屏幕上看到有意义的内容 - " + renderTimeMs);
- }
- @Override
- public void onDroppedVideoFrames(EventTime eventTime, int droppedFrames, long elapsedMs) {
- AnalyticsListener.super.onDroppedVideoFrames(eventTime, droppedFrames, elapsedMs);
- Log.i(TAG, "AnalyticsListener - onDroppedVideoFrames - 视频丢帧 - " + droppedFrames);
- }
- @Override
- public void onAudioUnderrun(EventTime eventTime, int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
- AnalyticsListener.super.onAudioUnderrun(eventTime, bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
- Log.i(TAG, "AnalyticsListener - onAudioUnderrun - 音频欠载 - " + bufferSizeMs);
- }
- };
- exoPlayer.addAnalyticsListener(analyticsListener);
- }
复制代码 自定义ExoPlayer界面
按住Command键,左键查看PlayerView.java,鼠标hover在文件上,看它的位置,可以顺藤摸瓜找到它的源码
布局界面在这里
复制到本身的项目中
在此基础上更改custom_player_control_view.xml
MediaSessionService后台播放
参考文章
实现效果
让媒体挂在后台播放,如下图
正如官网所说,可以用在长视频,听视频。显然上面没有许多的交互,假如短视频用这个,既看不到播放的内容,也没啥交互,就还是不用MediaSession
案例实现
按照官方设置的步调,语言还是采用Java。将构建一个MediaSession,让他在后台一直播放,同时可以自定义几个按钮,如点赞,收藏。末了监听视频是否播放。
MediaController
显然需要用到MediaSessionService和MediaController
创建项目
按照步调,先new 一个ListenableFuture controllerFuture
先引入依赖
- implementation ("androidx.media3:media3-session:1.3.1")
- implementation ("androidx.media3:media3-exoplayer:1.3.1")
- implementation ("androidx.media3:media3-ui:1.3.1")
- implementation ("androidx.media3:media3-common:1.3.1")
复制代码
定义MediaController
- package com.test.mediasessionserviceexample;
- import androidx.appcompat.app.AppCompatActivity;
- import androidx.media3.common.MediaItem;
- import androidx.media3.common.MediaMetadata;
- import androidx.media3.session.MediaController;
- import androidx.media3.session.SessionToken;
- import android.content.ComponentName;
- import android.net.Uri;
- import android.os.Bundle;
- import com.google.common.util.concurrent.ListenableFuture;
- import com.google.common.util.concurrent.MoreExecutors;
- import java.util.concurrent.ExecutionException;
- public class MainActivity extends AppCompatActivity {
- ListenableFuture<MediaController> controllerFuture;
- private final String mediaUrl = "https://media.w3.org/2010/05/sintel/trailer.mp4";
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- SessionToken sessionToken = new SessionToken(this, new ComponentName(this, 你的服务));
- controllerFuture = new MediaController.Builder(this, sessionToken).buildAsync();
- controllerFuture.addListener(() -> {
- Uri uri = Uri.parse(mediaUrl);
- MediaMetadata metadata = new MediaMetadata.Builder()
- .setArtist("阿笙")
- .setTitle("我名字是视频")
- .build();
- MediaItem mediaItem = new MediaItem.Builder()
- .setMediaId("media-one")
- .setUri(uri)
- .setMediaMetadata(metadata).build();
- try {
- MediaController mediaController = controllerFuture.get();
- mediaController.setMediaItem(mediaItem);
- mediaController.prepare();
- } catch (ExecutionException | InterruptedException e) {
- throw new RuntimeException(e);
- }
- }, MoreExecutors.directExecutor());
- }
- @Override
- protected void onStop() {
- super.onStop();
- MediaController.releaseFuture(controllerFuture);
- }
- }
复制代码 定义服务MediaSessionService
- package com.test.mediasessionserviceexample;
- import android.content.Intent;
- import androidx.annotation.NonNull;
- import androidx.annotation.Nullable;
- import androidx.media3.common.Player;
- import androidx.media3.exoplayer.ExoPlayer;
- import androidx.media3.session.MediaSession;
- import androidx.media3.session.MediaSession.ControllerInfo;
- import androidx.media3.session.MediaSessionService;
- public class PlaybackService extends MediaSessionService {
- private MediaSession mediaSession = null;
- @Override
- public void onCreate() {
- super.onCreate();
- ExoPlayer player = new ExoPlayer.Builder(this).build();
- mediaSession = new MediaSession.Builder(this, player).build();
- }
- @Nullable
- @Override
- public MediaSession onGetSession(@NonNull ControllerInfo controllerInfo) {
- return mediaSession;
- }
- @Override
- public void onTaskRemoved(Intent rootIntent) {
- super.onTaskRemoved(rootIntent);
- Player player = mediaSession.getPlayer();
- if (!player.getPlayWhenReady() || player.getMediaItemCount() == 0 || player.getPlaybackState() == Player.STATE_ENDED) {
- stopSelf();
- }
- }
- @Override
- public void onDestroy() {
- super.onDestroy();
- mediaSession.getPlayer().release();
- mediaSession.release();
- mediaSession = null;
- }
- }
复制代码
注册服务/申明服务
- <service android:name=".PlaybackService"
- android:foregroundServiceType="mediaPlayback"
- android:exported="true">
- <intent-filter>
- <action android:name="androidx.media3.session.MediaSessionService"/>
- </intent-filter>
- </service>
复制代码 申明权限(网络、前台服务)
- <uses-permission android:name="android.permission.INTERNET" />
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
复制代码
运行项目
下拉通知,可以看到这个服务,
定义按钮CommandButton
引入图标
- private CommandButton cBRemoveFromLikes;
- private CommandButton cBRemoveFromFavorites;
- private CommandButton cBAddToLikes;
- private CommandButton cBAddToFavorites;
- private SessionCommand sCAddToLikes;
- private SessionCommand sCAddToFavorites;
- private SessionCommand sCRemoveFromLike;
- private SessionCommand sCRemoveFromFavorite;
- private static final String SAVE_TO_LIKES = "save to likes";
- private static final String SAVE_TO_FAVORITES = "save to favorites";
- private static final String REMOVE_FROM_LIKES = "remove from likes";
- private static final String REMOVE_FROM_FAVORITES = "remove from favorites";
复制代码- sCAddToLikes = new SessionCommand(SAVE_TO_LIKES, new Bundle());
- sCAddToFavorites = new SessionCommand(SAVE_TO_FAVORITES, new Bundle());
- sCRemoveFromLike = new SessionCommand(REMOVE_FROM_LIKES, new Bundle());
- sCRemoveFromFavorite = new SessionCommand(REMOVE_FROM_FAVORITES, new Bundle());
- cBAddToLikes = buildCommandButton(SAVE_TO_LIKES, R.drawable.like_icon, sCAddToLikes);
- cBAddToFavorites = buildCommandButton(SAVE_TO_FAVORITES, R.drawable.favorite_icon, sCAddToFavorites);
- cBRemoveFromLikes = buildCommandButton(REMOVE_FROM_LIKES, R.drawable.like_remove_icon, sCRemoveFromLike);
- cBRemoveFromFavorites = buildCommandButton(REMOVE_FROM_FAVORITES, R.drawable.favorite_remove_icon, sCRemoveFromFavorite);
复制代码- private CommandButton buildCommandButton(String displayName, int iconResId, SessionCommand sessionCommand) {
- return new CommandButton.Builder()
- .setDisplayName(displayName)
- .setIconResId(iconResId)
- .setSessionCommand(sessionCommand)
- .build();
- }
复制代码 自定义MediaSession的布局
定义MediaSession可以使用哪些自定义命令
在MediaSession.Builder()中setCallback 回调,setCustomLayout布局
通过吸收customAction的值,判断当前是什么命令,对应设置布局
- class CustomMediaSessionCallback implements MediaSession.Callback {
- @OptIn(markerClass = UnstableApi.class)
- @NonNull
- @Override
- public ConnectionResult onConnect(@NonNull MediaSession session, @NonNull ControllerInfo controller) {
- SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
- .add(sCAddToLikes)
- .add(sCAddToFavorites)
- .add(sCRemoveFromLike)
- .add(sCRemoveFromFavorite)
- .build();
- return new ConnectionResult.AcceptedResultBuilder(session)
- .setAvailableSessionCommands(sessionCommands)
- .build();
- }
- @OptIn(markerClass = UnstableApi.class)
- @NonNull
- @Override
- public ListenableFuture<SessionResult> onCustomCommand(@NonNull MediaSession session, @NonNull ControllerInfo controller, @NonNull SessionCommand customCommand, @NonNull Bundle args) {
- final CommandButton buttonOne = mediaSession.getCustomLayout().get(0);
- final CommandButton buttonTwo = mediaSession.getCustomLayout().get(1);
- switch (customCommand.customAction) {
- case SAVE_TO_LIKES:
- mediaSession.setCustomLayout(ImmutableList.of(cBRemoveFromLikes, buttonTwo));
- break;
- case SAVE_TO_FAVORITES:
- mediaSession.setCustomLayout(ImmutableList.of(buttonOne, cBRemoveFromFavorites));
- break;
- case REMOVE_FROM_LIKES:
- mediaSession.setCustomLayout(ImmutableList.of(cBAddToLikes, buttonTwo));
- break;
- case REMOVE_FROM_FAVORITES:
- mediaSession.setCustomLayout(ImmutableList.of(buttonOne, cBAddToFavorites));
- break;
- default:
- throw new RuntimeException("not implement");
- }
- return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS));
- }
- }
复制代码 运行测试
自定义Player的行为(play等)
- ExoPlayer player = new ExoPlayer.Builder(this).build();
- ForwardingPlayer forwardingPlayer = new ForwardingPlayer(player) {
- @Override
- public void play() {
- super.play();
- Log.d("ForwardingPlayer Log", "play!");
- }
- };
- mediaSession = new MediaSession.Builder(this, forwardingPlayer)
- .setCallback(new CustomMediaSessionCallback())
- .setCustomLayout(ImmutableList.of(cBAddToLikes, cBAddToFavorites))
- .build();
复制代码 MediaLibraryService
实现目标
参考文档
主要实现三个函数
- onGetLibraryRoot()
- onGetChildren()
- onGetSearchResult()
实现效果
创建一个目录,目录下有三个文件,并且可以根据标题搜索这三个文件
案例实现
采用JAVA实现
创建项目接着引入依赖
- implementation ("androidx.media3:media3-session:1.3.1")
- implementation ("androidx.media3:media3-exoplayer:1.3.1")
- implementation ("androidx.media3:media3-ui:1.3.1")
- implementation ("androidx.media3:media3-common:1.3.1")
复制代码 初始化MediaLibraryService
和MediaSession利用差不多,先实例
- package com.test.medialibraryservicetestapplication;
- import androidx.annotation.NonNull;
- import androidx.annotation.Nullable;
- import androidx.media3.common.MediaItem;
- import androidx.media3.exoplayer.ExoPlayer;
- import androidx.media3.session.LibraryResult;
- import androidx.media3.session.MediaLibraryService;
- import androidx.media3.session.MediaSession;
- import com.google.common.collect.ImmutableList;
- import com.google.common.util.concurrent.ListenableFuture;
- public class PlaybackService extends MediaLibraryService {
- MediaLibrarySession mediaLibrarySession = null;
- MediaLibrarySession.Callback callback = new MediaLibrarySession.Callback() {
- @NonNull
- @Override
- public ListenableFuture<LibraryResult<MediaItem>> onGetLibraryRoot(@NonNull MediaLibrarySession session, @NonNull MediaSession.ControllerInfo browser, @Nullable MediaLibraryService.LibraryParams params) {
- return MediaLibrarySession.Callback.super.onGetLibraryRoot(session, browser, params);
- }
- @NonNull
- @Override
- public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> onGetChildren(MediaLibrarySession session, @NonNull MediaSession.ControllerInfo browser, @NonNull String parentId, int page, int pageSize, @Nullable MediaLibraryService.LibraryParams params) {
- return MediaLibrarySession.Callback.super.onGetChildren(session, browser, parentId, page, pageSize, params);
- }
- @NonNull
- @Override
- public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> onGetSearchResult(@NonNull MediaLibrarySession session, @NonNull MediaSession.ControllerInfo browser, @NonNull String query, int page, int pageSize, @Nullable MediaLibraryService.LibraryParams params) {
- return MediaLibrarySession.Callback.super.onGetSearchResult(session, browser, query, page, pageSize, params);
- }
- };
- @Nullable
- @Override
- public MediaLibrarySession onGetSession(@NonNull MediaSession.ControllerInfo controllerInfo) {
- return mediaLibrarySession;
- }
- @Override
- public void onCreate() {
- super.onCreate();
- ExoPlayer player = new ExoPlayer.Builder(this).build();
- mediaLibrarySession = new MediaLibraryService.MediaLibrarySession.Builder(this, player, callback).build();
- }
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (mediaLibrarySession != null) {
- mediaLibrarySession.getPlayer().release();
- mediaLibrarySession.release();
- mediaLibrarySession = null;
- }
- }
- }
复制代码 创建树形结构存储数据
要获取根对象,需要有一个建好的树形数据,就如下面这幅图上,它是一个树,树的根节点RootNode,分叉出去好几个类目,简单起见,这里先弄简单的Root->Music/Game/Study
定义树
构建根节点,添加三个子节点到根节点
末了改为单例
- package com.test.medialibraryservicetestapplication;
- import java.util.HashMap;
- import java.util.Map;
- public class Tree {
- Map<String, TreeNode> treeNodes;
- final String ROOT_ID = "root";
- final String MUSIC_ID = "music";
- final String GAME_ID = "game";
- final String STUDY_ID = "study";
- final String ROOT_TITLE = "root title";
- final String MUSIC_TITLE = "music title";
- final String GAME_TITLE = "game title";
- final String STUDY_TITLE = "study title";
- private final static Tree instance = new Tree();
- public static Tree genInstance() {
- return instance;
- }
- private boolean isInitialized = false;
- private Tree() {
- if (!isInitialized) {
- isInitialized = true;
- treeNodes = new HashMap<>();
- TreeNode rootNode = new TreeNode(ROOT_ID, ROOT_TITLE);
- TreeNode musicNode = new TreeNode(MUSIC_ID, MUSIC_TITLE);
- TreeNode gameNode = new TreeNode(GAME_ID, GAME_TITLE);
- TreeNode studyNode = new TreeNode(STUDY_ID, STUDY_TITLE);
- rootNode.children.add(musicNode.node);
- rootNode.children.add(gameNode.node);
- rootNode.children.add(studyNode.node);
- treeNodes.put(ROOT_ID, rootNode);
- }
- }
- }
复制代码 获取根节点
- MediaItem getTreeRoot() {
- return Objects.requireNonNull(treeNodes.get(ROOT_ID)).node;
- }
复制代码- @NonNull
- @Override
- public ListenableFuture<LibraryResult<MediaItem>> onGetLibraryRoot(@NonNull MediaLibrarySession session, @NonNull MediaSession.ControllerInfo browser, @Nullable MediaLibraryService.LibraryParams params) {
- return Futures.immediateFuture(LibraryResult.ofItem(Tree.genInstance().getTreeRoot(), params));
- }
复制代码 获取结点的子节点集合
- List<MediaItem> getNodeChildren(String treeNodeId) {
- return Objects.requireNonNull(treeNodes.get(treeNodeId)).children;
- }
复制代码- @NonNull
- @Override
- public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> onGetChildren(MediaLibrarySession session, @NonNull MediaSession.ControllerInfo browser, @NonNull String parentId, int page, int pageSize, @Nullable MediaLibraryService.LibraryParams params) {
- List<MediaItem> children = Tree.genInstance().getNodeChildren(parentId);
- if (!children.isEmpty()) {
- return Futures.immediateFuture(LibraryResult.ofItemList(children, params));
- }
- return Futures.immediateFuture(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
- }
复制代码 注册Service及申明权限
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
复制代码- <service
- android:name=".PlaybackService"
- android:exported="true"
- android:foregroundServiceType="mediaPlayback">
- <intent-filter>
- <action android:name="androidx.media3.session.MediaSessionService" />
- </intent-filter>
- </service>
复制代码 MediaBrowser浏览MediaLibraryService定义的媒体库
获取MediaLibraryService构建的媒体库的根节点
获取某一节点下的子节点
运行测试
运行项目,并在60行打上断点,看到value属性值为三个MediaItem对象
可以看到获取到根节点下的三个子节点
- package com.test.medialibraryservicetestapplication;import androidx.annotation.NonNull;import androidx.annotation.Nullable;import androidx.media3.common.MediaItem;import androidx.media3.exoplayer.ExoPlayer;import androidx.media3.session.LibraryResult;import androidx.media3.session.MediaLibraryService;import androidx.media3.session.MediaSession;import com.google.common.collect.ImmutableList;import com.google.common.util.concurrent.Futures;import com.google.common.util.concurrent.ListenableFuture;import java.util.List;public class PlaybackService extends MediaLibraryService { MediaLibrarySession mediaLibrarySession = null; MediaLibrarySession.Callback callback = new MediaLibrarySession.Callback() { @NonNull
- @Override
- public ListenableFuture<LibraryResult<MediaItem>> onGetLibraryRoot(@NonNull MediaLibrarySession session, @NonNull MediaSession.ControllerInfo browser, @Nullable MediaLibraryService.LibraryParams params) {
- return Futures.immediateFuture(LibraryResult.ofItem(Tree.genInstance().getTreeRoot(), params));
- }
- @NonNull
- @Override
- public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> onGetChildren(MediaLibrarySession session, @NonNull MediaSession.ControllerInfo browser, @NonNull String parentId, int page, int pageSize, @Nullable MediaLibraryService.LibraryParams params) {
- List<MediaItem> children = Tree.genInstance().getNodeChildren(parentId);
- if (!children.isEmpty()) {
- return Futures.immediateFuture(LibraryResult.ofItemList(children, params));
- }
- return Futures.immediateFuture(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
- }
- @NonNull @Override public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> onGetSearchResult(@NonNull MediaLibrarySession session, @NonNull MediaSession.ControllerInfo browser, @NonNull String query, int page, int pageSize, @Nullable MediaLibraryService.LibraryParams params) { return MediaLibrarySession.Callback.super.onGetSearchResult(session, browser, query, page, pageSize, params); } }; @Nullable @Override public MediaLibrarySession onGetSession(@NonNull MediaSession.ControllerInfo controllerInfo) { return mediaLibrarySession; } @Override public void onCreate() { super.onCreate(); ExoPlayer player = new ExoPlayer.Builder(this).build(); mediaLibrarySession = new MediaLibraryService.MediaLibrarySession.Builder(this, player, callback).build(); } @Override public void onDestroy() { super.onDestroy(); if (mediaLibrarySession != null) { mediaLibrarySession.getPlayer().release(); mediaLibrarySession.release(); mediaLibrarySession = null; } }}
复制代码 搜索媒体库
首先还是去Tree中定义搜索的函数
- List<MediaItem> search(String query) {
- List<MediaItem> titleMatches = new ArrayList<>();
- Object[] words = Arrays.stream(query.split(" ")).map(it -> it.trim().toLowerCase()).filter(it -> it.length() > 1).toArray();
- titleNodes.keySet().forEach(title -> {
- TreeNode treeNode = titleNodes.get(title);
- for (Object word : words) {
- boolean contains = title.contains((CharSequence) word);
- if (contains) {
- assert treeNode != null;
- titleMatches.add(treeNode.node);
- }
- }
- });
- return titleMatches;
- }
复制代码- public static Map<String, TreeNode> titleNodes;
- titleNodes = new HashMap<>();
- titleNodes.put(MUSIC_TITLE, musicNode);
- titleNodes.put(GAME_TITLE, gameNode);
- titleNodes.put(STUDY_TITLE, studyNode);
复制代码
- @NonNull
- @Override
- public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> onGetSearchResult(@NonNull MediaLibrarySession session, @NonNull MediaSession.ControllerInfo browser, @NonNull String query, int page, int pageSize, @Nullable MediaLibraryService.LibraryParams params) {
- return Futures.immediateFuture(LibraryResult.ofItemList(Tree.genInstance().search(query), params));
- }
复制代码 MediaBrowser 搜索
- private void searchMedia(String query) {
- ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> searchFuture = mediaBrowser.getSearchResult(query, 0, Integer.MAX_VALUE, null);
- searchFuture.addListener(() -> {
- try {
- LibraryResult<ImmutableList<MediaItem>> immutableListLibraryResult = searchFuture.get();
- ImmutableList<MediaItem> value = immutableListLibraryResult.value;
- } catch (ExecutionException | InterruptedException e) {
- throw new RuntimeException(e);
- }
- }, MoreExecutors.directExecutor());
- }
复制代码
搜索“ga”,可以查到“game”这个MediaItem
填坑中…
内容是真的多!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |