最后监听视频是否播放
发布时间:2025-06-24 20:16:53 作者:北方职教升学中心 阅读量:047
最后监听视频是否播放。将构建一个MediaSession,让他在后台一直播放,同时可以自定义几个按钮,如点赞,收藏。
相关文章
【重生之我在学Android原生】ContentProvider(Java)
【重生之我在学Android原生】Media3
前言
内容颇多,尽量从简
ExoPlayer使用
官方文档
参考文章
实现效果
Android(java)
使用ExoPlayer播放视频,自定义ExoPlayer界面,记录播放位置(横屏竖屏切换/切换至后台等)
案例实现
创建项目
添加依赖
Sync 一下
/// Jetpack Media3 ExoPlayerimplementation("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.ConstraintLayoutxmlns: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.PlayerViewandroid: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的初始化销毁等
packagecom.test.exoplayerexampleapplication;importandroidx.annotation.OptIn;importandroidx.appcompat.app.AppCompatActivity;importandroidx.media3.common.util.UnstableApi;importandroidx.media3.common.util.Util;importandroidx.media3.ui.PlayerView;importandroid.os.Bundle;publicclassMainActivityextendsAppCompatActivity{@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}@OptIn(markerClass =UnstableApi.class)@OverrideprotectedvoidonStart(){super.onStart();if(Util.SDK_INT>=24){initializePlayer();}}@OptIn(markerClass =UnstableApi.class)@OverrideprotectedvoidonResume(){super.onResume();if(Util.SDK_INT<24){initializePlayer();}}privatevoidinitializePlayer(){}}
定义PlayerView,ExoPlayer
privatePlayerViewplayerView;privateExoPlayerexoPlayer;privatefinalUrivideoOneUri =Uri.parse("http://www.w3school.com.cn/example/html5/mov_bbb.mp4");
在AndroidManifest.xml中定义网络权限,如果链接是Http,还需加userCleartextTraffic
<uses-permissionandroid:name="android.permission.INTERNET"/>
android:usesCleartextTraffic="true"
定义initializePlayer()方法
privatevoidinitializePlayer(){playerView =findViewById(R.id.video_view);exoPlayer =newExoPlayer.Builder(this).build();playerView.setPlayer(exoPlayer);MediaItemmediaItem =MediaItem.fromUri(videoOneUri);exoPlayer.addMediaItem(mediaItem);exoPlayer.prepare();}
运行可以播放视频
释放资源
媒体播放器是很占用资源的,所以当不再需要播放器时,要释放它,当然也要在Activity的生命周期中释放资源
在onStop和onPause适当调用
@OptIn(markerClass =UnstableApi.class)@OverrideprotectedvoidonPause(){super.onPause();if(Util.SDK_INT<24){releasePlayer();}}@OptIn(markerClass =UnstableApi.class)@OverrideprotectedvoidonStop(){super.onStop();if(Util.SDK_INT>=24){releasePlayer();}}privatevoidreleasePlayer(){exoPlayer.release();}
记录视频的播放状态
重新运行项目,测试切换至后台
切换至后台的生命周期,可以知道当APP放到后台,会调用player.release()方法释放
我们发现视频切换到后台,再切换回来,之前的播放到第四秒,切换回来发现,又回到第0秒。
按照官网配置Preferences DataStore
引入依赖
/// Preferences DataStoreimplementation("androidx.datastore:datastore-preferences:1.0.0")/// RxJava2 supportimplementation("androidx.datastore:datastore-preferences-rxjava2:1.0.0")/// RxJava3 supportimplementation("androidx.datastore:datastore-preferences-rxjava3:1.0.0")
实例RxDataStore,销毁
privateRxDataStore<Preferences>dataStore;
if(dataStore ==null){dataStore =newRxPreferenceDataStoreBuilder(this,"ExoPlayerKeys").build();}
@OverrideprotectedvoidonDestroy(){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存储值
privatevoidsaveVideoRecord(){dataStore.updateDataAsync(preferences ->{MutablePreferencesmutablePreferences =preferences.toMutablePreferences();mutablePreferences.set(currentMediaItemIndexPK,currentMediaItemIndex);mutablePreferences.set(playbackPositionPK,playbackPosition.intValue());mutablePreferences.set(playWhenReadyPK,playWhenReady);returnSingle.just(mutablePreferences);});}
在onCreate()中取出之前存储的值,首次运行时,是没有存值的,所以会异常NULL,所以没有值就初始化这些变量
try{currentMediaItemIndex =dataStore.data().map(preferences ->preferences.get(currentMediaItemIndexPK)).blockingFirst();}catch(Exceptione){currentMediaItemIndex =0;}try{playbackPosition =Long.valueOf(dataStore.data().map(preferences ->preferences.get(playbackPositionPK)).blockingFirst());}catch(Exceptione){playbackPosition =0L;}try{playWhenReady =dataStore.data().map(preferences ->preferences.get(playWhenReadyPK)).blockingFirst();}catch(Exceptione){playWhenReady =false;}
删除APP,重新运行项目,旋转屏幕也能记录播放状态,和播放进度,播放第几个视频
旋转后,仍然从之前播放的进度继续,保持暂停。
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
packagecom.test.mediasessionserviceexample;importandroidx.appcompat.app.AppCompatActivity;importandroidx.media3.common.MediaItem;importandroidx.media3.common.MediaMetadata;importandroidx.media3.session.MediaController;importandroidx.media3.session.SessionToken;importandroid.content.ComponentName;importandroid.net.Uri;importandroid.os.Bundle;importcom.google.common.util.concurrent.ListenableFuture;importcom.google.common.util.concurrent.MoreExecutors;importjava.util.concurrent.ExecutionException;publicclassMainActivityextendsAppCompatActivity{ListenableFuture<MediaController>controllerFuture;privatefinalStringmediaUrl ="https://media.w3.org/2010/05/sintel/trailer.mp4";@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);SessionTokensessionToken =newSessionToken(this,newComponentName(this,你的服务));controllerFuture =newMediaController.Builder(this,sessionToken).buildAsync();controllerFuture.addListener(()->{Uriuri =Uri.parse(mediaUrl);MediaMetadatametadata =newMediaMetadata.Builder().setArtist("阿笙").setTitle("我名字是视频").build();MediaItemmediaItem =newMediaItem.Builder().setMediaId("media-one").setUri(uri).setMediaMetadata(metadata).build();try{MediaControllermediaController =controllerFuture.get();mediaController.setMediaItem(mediaItem);mediaController.prepare();}catch(ExecutionException|InterruptedExceptione){thrownewRuntimeException(e);}},MoreExecutors.directExecutor());}@OverrideprotectedvoidonStop(){super.onStop();MediaController.releaseFuture(controllerFuture);}}
定义服务MediaSessionService
packagecom.test.mediasessionserviceexample;importandroid.content.Intent;importandroidx.annotation.NonNull;importandroidx.annotation.Nullable;importandroidx.media3.common.Player;importandroidx.media3.exoplayer.ExoPlayer;importandroidx.media3.session.MediaSession;importandroidx.media3.session.MediaSession.ControllerInfo;importandroidx.media3.session.MediaSessionService;publicclassPlaybackServiceextendsMediaSessionService{privateMediaSessionmediaSession =null;@OverridepublicvoidonCreate(){super.onCreate();ExoPlayerplayer =newExoPlayer.Builder(this).build();mediaSession =newMediaSession.Builder(this,player).build();}@Nullable@OverridepublicMediaSessiononGetSession(@NonNullControllerInfocontrollerInfo){returnmediaSession;}@OverridepublicvoidonTaskRemoved(IntentrootIntent){super.onTaskRemoved(rootIntent);Playerplayer =mediaSession.getPlayer();if(!player.getPlayWhenReady()||player.getMediaItemCount()==0||player.getPlaybackState()==Player.STATE_ENDED){stopSelf();}}@OverridepublicvoidonDestroy(){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>
申明权限(网络、
既然没有onDestory掉Activity
那么定义三个变量(播放位置,播放状态,第几个视频)
privateLongplaybackPosition =0L;privateintcurrentMediaItemIndex =0;privatebooleanplayWhenReady =false;
playbackPosition =exoPlayer.getCurrentPosition();currentMediaItemIndex =exoPlayer.getCurrentMediaItemIndex();playWhenReady =exoPlayer.getPlayWhenReady();
exoPlayer.setPlayWhenReady(playWhenReady);exoPlayer.seekTo(currentMediaItemIndex,playbackPosition);
添加新的视频链接
privatefinalUrivideoTwoUri =Uri.parse("https://media.w3.org/2010/05/sintel/trailer.mp4");
运行测试,播放下一个视频,并播放至一半,切换到后台,再切换回来
横竖屏切换
横竖屏切换的生命周期,Activity被Destory了
使用SharedPreference来存储键值对,参考官方文档,官方推荐用DataStore
那使用DataStore
我们现在做的事情是,使用DataStore Preference来存储视频在释放之前的播放进度,播放状态,播放到第几个视频了。
监听事件
参考链接
@OptIn(markerClass =UnstableApi.class)privatevoidaddPlayerListener(){listener =newPlayer.Listener(){@OptIn(markerClass =UnstableApi.class)@OverridepublicvoidonPlaybackStateChanged(intplaybackState){Player.Listener.super.onPlaybackStateChanged(playbackState);StringstateString ="UNKNOWN_STATE";if(playbackState ==ExoPlayer.STATE_IDLE){stateString ="EXoPlayer.STATE_IDLE";}elseif(playbackState ==ExoPlayer.STATE_BUFFERING){stateString ="ExoPlayer.STATE_BUFFERING";}elseif(playbackState ==Player.STATE_READY){stateString ="ExoPlayer.STATE_READY";}elseif(playbackState ==Player.STATE_ENDED){stateString ="ExoPlayer.STATE_ENDED";}Log.d(TAG,"changed state to "+stateString);}};exoPlayer.addListener(listener);analyticsListener =newAnalyticsListener(){@OverridepublicvoidonRenderedFirstFrame(EventTimeeventTime,Objectoutput,longrenderTimeMs){AnalyticsListener.super.onRenderedFirstFrame(eventTime,output,renderTimeMs);Log.i(TAG,"AnalyticsListener - onRenderedFirstFrame - 等待多长时间才能在屏幕上看到有意义的内容 - "+renderTimeMs);}@OverridepublicvoidonDroppedVideoFrames(EventTimeeventTime,intdroppedFrames,longelapsedMs){AnalyticsListener.super.onDroppedVideoFrames(eventTime,droppedFrames,elapsedMs);Log.i(TAG,"AnalyticsListener - onDroppedVideoFrames - 视频丢帧 - "+droppedFrames);}@OverridepublicvoidonAudioUnderrun(EventTimeeventTime,intbufferSize,longbufferSizeMs,longelapsedSinceLastFeedMs){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后台播放
参考文章
实现效果
让媒体挂在后台播放,如下图
正如官网所说,可以用在长视频,听视频。所以,现在需要记录之前播放的位置,播放的状态,播放到第几个视频了。前台服务)
<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
引入图标
privateCommandButtoncBRemoveFromLikes;privateCommandButtoncBRemoveFromFavorites;privateCommandButtoncBAddToLikes;privateCommandButtoncBAddToFavorites;privateSessionCommandsCAddToLikes;privateSessionCommandsCAddToFavorites;privateSessionCommandsCRemoveFromLike;privateSessionCommandsCRemoveFromFavorite;privatestaticfinalStringSAVE_TO_LIKES="save to likes";privatestaticfinalStringSAVE_TO_FAVORITES="save to favorites";privatestaticfinalStringREMOVE_FROM_LIKES="remove from likes";privatestaticfinalStringREMOVE_FROM_FAVORITES="remove from favorites";
sCAddToLikes =newSessionCommand(SAVE_TO_LIKES,newBundle());sCAddToFavorites =newSessionCommand(SAVE_TO_FAVORITES,newBundle());sCRemoveFromLike =newSessionCommand(REMOVE_FROM_LIKES,newBundle());sCRemoveFromFavorite =newSessionCommand(REMOVE_FROM_FAVORITES,newBundle());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);
privateCommandButtonbuildCommandButton(StringdisplayName,inticonResId,SessionCommandsessionCommand){returnnewCommandButton.Builder().setDisplayName(displayName).setIconResId(iconResId).setSessionCommand(sessionCommand).build();}
自定义MediaSession的布局
定义MediaSession可以使用哪些自定义命令
在MediaSession.Builder()中setCallback 回调,setCustomLayout布局
通过接收customAction的值,判断当前是什么命令,对应设置布局
classCustomMediaSessionCallbackimplementsMediaSession.Callback{@OptIn(markerClass =UnstableApi.class)@NonNull@OverridepublicConnectionResultonConnect(@NonNullMediaSessionsession,@NonNullControllerInfocontroller){SessionCommandssessionCommands =ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon().add(sCAddToLikes).add(sCAddToFavorites).add(sCRemoveFromLike).add(sCRemoveFromFavorite).build();returnnewConnectionResult.AcceptedResultBuilder(session).setAvailableSessionCommands(sessionCommands).build();}@OptIn(markerClass =UnstableApi.class)@NonNull@OverridepublicListenableFuture<SessionResult>onCustomCommand(@NonNullMediaSessionsession,@NonNullControllerInfocontroller,@NonNullSessionCommandcustomCommand,@NonNullBundleargs){finalCommandButtonbuttonOne =mediaSession.getCustomLayout().get(0);finalCommandButtonbuttonTwo =mediaSession.getCustomLayout().get(1);switch(customCommand.customAction){caseSAVE_TO_LIKES:mediaSession.setCustomLayout(ImmutableList.of(cBRemoveFromLikes,buttonTwo));break;caseSAVE_TO_FAVORITES:mediaSession.setCustomLayout(ImmutableList.of(buttonOne,cBRemoveFromFavorites));break;caseREMOVE_FROM_LIKES:mediaSession.setCustomLayout(ImmutableList.of(cBAddToLikes,buttonTwo));break;caseREMOVE_FROM_FAVORITES:mediaSession.setCustomLayout(ImmutableList.of(buttonOne,cBAddToFavorites));break;default:thrownewRuntimeException("not implement");}returnFutures.immediateFuture(newSessionResult(SessionResult.RESULT_SUCCESS));}}
运行测试
自定义Player的行为(play等)
ExoPlayerplayer =newExoPlayer.Builder(this).build();ForwardingPlayerforwardingPlayer =newForwardingPlayer(player){@Overridepublicvoidplay(){super.play();Log.d("ForwardingPlayer Log","play!");}};mediaSession =newMediaSession.Builder(this,forwardingPlayer).setCallback(newCustomMediaSessionCallback()).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操作差不多,先实例
packagecom.test.medialibraryservicetestapplication;importandroidx.annotation.NonNull;importandroidx.annotation.Nullable;importandroidx.media3.common.MediaItem;importandroidx.media3.exoplayer.ExoPlayer;importandroidx.media3.session.LibraryResult;importandroidx.media3.session.MediaLibraryService;importandroidx.media3.session.MediaSession;importcom.google.common.collect.ImmutableList;importcom.google.common.util.concurrent.ListenableFuture;publicclassPlaybackServiceextendsMediaLibraryService{MediaLibrarySessionmediaLibrarySession =null;MediaLibrarySession.Callbackcallback =newMediaLibrarySession.Callback(){@NonNull@OverridepublicListenableFuture<LibraryResult<MediaItem>>onGetLibraryRoot(@NonNullMediaLibrarySessionsession,@NonNullMediaSession.ControllerInfobrowser,@NullableMediaLibraryService.LibraryParamsparams){returnMediaLibrarySession.Callback.super.onGetLibraryRoot(session,browser,params);}@NonNull@OverridepublicListenableFuture<LibraryResult<ImmutableList<MediaItem>>>onGetChildren(MediaLibrarySessionsession,@NonNullMediaSession.ControllerInfobrowser,@NonNullStringparentId,intpage,intpageSize,@NullableMediaLibraryService.LibraryParamsparams){returnMediaLibrarySession.Callback.super.onGetChildren(session,browser,parentId,page,pageSize,params);}@NonNull@OverridepublicListenableFuture<LibraryResult<ImmutableList<MediaItem>>>onGetSearchResult(@NonNullMediaLibrarySessionsession,@NonNullMediaSession.ControllerInfobrowser,@NonNullStringquery,intpage,intpageSize,@NullableMediaLibraryService.LibraryParamsparams){returnMediaLibrarySession.Callback.super.onGetSearchResult(session,browser,query,page,pageSize,params);}};@Nullable@OverridepublicMediaLibrarySessiononGetSession(@NonNullMediaSession.ControllerInfocontrollerInfo){returnmediaLibrarySession;}@OverridepublicvoidonCreate(){super.onCreate();ExoPlayerplayer =newExoPlayer.Builder(this).build();mediaLibrarySession =newMediaLibraryService.MediaLibrarySession.Builder(this,player,callback).build();}@OverridepublicvoidonDestroy(){super.onDestroy();if(mediaLibrarySession !=null){mediaLibrarySession.getPlayer().release();mediaLibrarySession.release();mediaLibrarySession =null;}}}
创建树形结构存储数据
要获取根对象,需要有一个建好的树形数据,就如下面这幅图上,它是一个树,树的根节点RootNode,分叉出去好几个类目,简单起见,这里先弄简单的Root->Music/Game/Study
定义树
构建根节点,添加三个子节点到根节点
最后改为单例
packagecom.test.medialibraryservicetestapplication;importjava.util.HashMap;importjava.util.Map;publicclassTree{Map<String,TreeNode>treeNodes;finalStringROOT_ID="root";finalStringMUSIC_ID="music";finalStringGAME_ID="game";finalStringSTUDY_ID="study";finalStringROOT_TITLE="root title";finalStringMUSIC_TITLE="music title";finalStringGAME_TITLE="game title";finalStringSTUDY_TITLE="study title";privatefinalstaticTreeinstance =newTree();publicstaticTreegenInstance(){returninstance;}privatebooleanisInitialized =false;privateTree(){if(!isInitialized){isInitialized =true;treeNodes =newHashMap<>();TreeNoderootNode =newTreeNode(ROOT_ID,ROOT_TITLE);TreeNodemusicNode =newTreeNode(MUSIC_ID,MUSIC_TITLE);TreeNodegameNode =newTreeNode(GAME_ID,GAME_TITLE);TreeNodestudyNode =newTreeNode(STUDY_ID,STUDY_TITLE);rootNode.children.add(musicNode.node);rootNode.children.add(gameNode.node);rootNode.children.add(studyNode.node);treeNodes.put(ROOT_ID,rootNode);}}}
获取根节点
MediaItemgetTreeRoot(){returnObjects.requireNonNull(treeNodes.get(ROOT_ID)).node;}
@NonNull@OverridepublicListenableFuture<LibraryResult<MediaItem>>onGetLibraryRoot(@NonNullMediaLibrarySessionsession,@NonNullMediaSession.ControllerInfobrowser,@NullableMediaLibraryService.LibraryParamsparams){returnFutures.immediateFuture(LibraryResult.ofItem(Tree.genInstance().getTreeRoot(),params));}
获取结点的子节点集合
List<MediaItem>getNodeChildren(StringtreeNodeId){returnObjects.requireNonNull(treeNodes.get(treeNodeId)).children;}
@NonNull@OverridepublicListenableFuture<LibraryResult<ImmutableList<MediaItem>>>onGetChildren(MediaLibrarySessionsession,@NonNullMediaSession.ControllerInfobrowser,@NonNullStringparentId,intpage,intpageSize,@NullableMediaLibraryService.LibraryParamsparams){List<MediaItem>children =Tree.genInstance().getNodeChildren(parentId);if(!children.isEmpty()){returnFutures.immediateFuture(LibraryResult.ofItemList(children,params));}returnFutures.immediateFuture(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));}
注册Service及申明权限
<uses-permissionandroid:name="android.permission.FOREGROUND_SERVICE"/><uses-permissionandroid:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
<serviceandroid:name=".PlaybackService"android:exported="true"android:foregroundServiceType="mediaPlayback"><intent-filter><actionandroid:name="androidx.media3.session.MediaSessionService"/></intent-filter></service>
MediaBrowser浏览MediaLibraryService定义的媒体库
获取MediaLibraryService构建的媒体库的根节点
获取某一节点下的子节点
运行测试
运行项目,并在60行打上断点,看到value属性值为三个MediaItem对象
可以看到获取到根节点下的三个子节点
packagecom.test.medialibraryservicetestapplication;importandroidx.annotation.NonNull;importandroidx.annotation.Nullable;importandroidx.media3.common.MediaItem;importandroidx.media3.exoplayer.ExoPlayer;importandroidx.media3.session.LibraryResult;importandroidx.media3.session.MediaLibraryService;importandroidx.media3.session.MediaSession;importcom.google.common.collect.ImmutableList;importcom.google.common.util.concurrent.Futures;importcom.google.common.util.concurrent.ListenableFuture;importjava.util.List;publicclassPlaybackServiceextendsMediaLibraryService{MediaLibrarySessionmediaLibrarySession =null;MediaLibrarySession.Callbackcallback =newMediaLibrarySession.Callback(){@NonNull@OverridepublicListenableFuture<LibraryResult<MediaItem>>onGetLibraryRoot(@NonNullMediaLibrarySessionsession,@NonNullMediaSession.ControllerInfobrowser,@NullableMediaLibraryService.LibraryParamsparams){returnFutures.immediateFuture(LibraryResult.ofItem(Tree.genInstance().getTreeRoot(),params));}@NonNull@OverridepublicListenableFuture<LibraryResult<ImmutableList<MediaItem>>>onGetChildren(MediaLibrarySessionsession,@NonNullMediaSession.ControllerInfobrowser,@NonNullStringparentId,intpage,intpageSize,@NullableMediaLibraryService.LibraryParamsparams){List<MediaItem>children =Tree.genInstance().getNodeChildren(parentId);if(!children.isEmpty()){returnFutures.immediateFuture(LibraryResult.ofItemList(children,params));}returnFutures.immediateFuture(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));}@NonNull@OverridepublicListenableFuture<LibraryResult<ImmutableList<MediaItem>>>onGetSearchResult(@NonNullMediaLibrarySessionsession,@NonNullMediaSession.ControllerInfobrowser,@NonNullStringquery,intpage,intpageSize,@NullableMediaLibraryService.LibraryParamsparams){returnMediaLibrarySession.Callback.super.onGetSearchResult(session,browser,query,page,pageSize,params);}};@Nullable@OverridepublicMediaLibrarySessiononGetSession(@NonNullMediaSession.ControllerInfocontrollerInfo){returnmediaLibrarySession;}@OverridepublicvoidonCreate(){super.onCreate();ExoPlayerplayer =newExoPlayer.Builder(this).build();mediaLibrarySession =newMediaLibraryService.MediaLibrarySession.Builder(this,player,callback).build();}@OverridepublicvoidonDestroy(){super.onDestroy();if(mediaLibrarySession !=null){mediaLibrarySession.getPlayer().release();mediaLibrarySession.release();mediaLibrarySession =null;}}}
搜索媒体库
首先还是去Tree中定义搜索的函数
List<MediaItem>search(Stringquery){List<MediaItem>titleMatches =newArrayList<>();Object[]words =Arrays.stream(query.split(" ")).map(it ->it.trim().toLowerCase()).filter(it ->it.length()>1).toArray();titleNodes.keySet().forEach(title ->{TreeNodetreeNode =titleNodes.get(title);for(Objectword :words){booleancontains =title.contains((CharSequence)word);if(contains){asserttreeNode !=null;titleMatches.add(treeNode.node);}}});returntitleMatches;}
publicstaticMap<String,TreeNode>titleNodes;titleNodes =newHashMap<>();titleNodes.put(MUSIC_TITLE,musicNode);titleNodes.put(GAME_TITLE,gameNode);titleNodes.put(STUDY_TITLE,studyNode);
@NonNull@OverridepublicListenableFuture<LibraryResult<ImmutableList<MediaItem>>>onGetSearchResult(@NonNullMediaLibrarySessionsession,@NonNullMediaSession.ControllerInfobrowser,@NonNullStringquery,intpage,intpageSize,@NullableMediaLibraryService.LibraryParamsparams){returnFutures.immediateFuture(LibraryResult.ofItemList(Tree.genInstance().search(query),params));}
MediaBrowser 搜索
privatevoidsearchMedia(Stringquery){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|InterruptedExceptione){thrownewRuntimeException(e);}},MoreExecutors.directExecutor());}
搜索“ga”,可以查到“game”这个MediaItem
填坑中…
内容是真的多!

案例实现
按照官方配置的步骤,语言还是采用Java。