前言
内容颇多,尽量从简
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
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定义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
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(); } } 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 dataStore;if (dataStore == null) { dataStore = new RxPreferenceDataStoreBuilder(this, "ExoPlayerKeys").build(); }@Override protected void onDestroy() { super.onDestroy(); if (dataStore != null) { dataStore.dispose(); } }
定义三个Key
Preferences.Key currentMediaItemIndexPK; Preferences.Key playbackPositionPK; Preferences.Key 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 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; } }
注册服务/申明服务
申明权限(网络、前台服务)
运行项目
下拉通知,可以看到这个服务,
定义按钮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 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 onGetLibraryRoot(@NonNull MediaLibrarySession session, @NonNull MediaSession.ControllerInfo browser, @Nullable MediaLibraryService.LibraryParams params) { return MediaLibrarySession.Callback.super.onGetLibraryRoot(session, browser, params); } @NonNull @Override public ListenableFuture 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 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 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 onGetLibraryRoot(@NonNull MediaLibrarySession session, @NonNull MediaSession.ControllerInfo browser, @Nullable MediaLibraryService.LibraryParams params) { return Futures.immediateFuture(LibraryResult.ofItem(Tree.genInstance().getTreeRoot(), params)); }
获取结点的子节点集合
List getNodeChildren(String treeNodeId) { return Objects.requireNonNull(treeNodes.get(treeNodeId)).children; }
@NonNull @Override public ListenableFuture onGetChildren(MediaLibrarySession session, @NonNull MediaSession.ControllerInfo browser, @NonNull String parentId, int page, int pageSize, @Nullable MediaLibraryService.LibraryParams params) { List 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及申明权限
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 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 onGetChildren(MediaLibrarySession session, @NonNull MediaSession.ControllerInfo browser, @NonNull String parentId, int page, int pageSize, @Nullable MediaLibraryService.LibraryParams params) { List 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 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 search(String query) { List 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 titleNodes; titleNodes = new HashMap(); titleNodes.put(MUSIC_TITLE, musicNode); titleNodes.put(GAME_TITLE, gameNode); titleNodes.put(STUDY_TITLE, studyNode);
@NonNull @Override public ListenableFuture 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 searchFuture = mediaBrowser.getSearchResult(query, 0, Integer.MAX_VALUE, null); searchFuture.addListener(() -> { try { LibraryResult immutableListLibraryResult = searchFuture.get(); ImmutableList value = immutableListLibraryResult.value; } catch (ExecutionException | InterruptedException e) { throw new RuntimeException(e); } }, MoreExecutors.directExecutor()); }
搜索“ga”,可以查到“game”这个MediaItem
填坑中…
内容是真的多!