【重生之我在学Android原生】Media3

server/2024/10/21 7:46:08/

前言

内容颇多,尽量从简

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.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.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的初始化销毁等
在这里插入图片描述
在这里插入图片描述

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 {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}@OptIn(markerClass = UnstableApi.class) @Overrideprotected void onStart() {super.onStart();if (Util.SDK_INT >= 24) {initializePlayer();}}@OptIn(markerClass = UnstableApi.class) @Overrideprotected 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) @Overrideprotected void onPause() {super.onPause();if (Util.SDK_INT < 24) {releasePlayer();}}@OptIn(markerClass = UnstableApi.class) @Overrideprotected 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 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,销毁

private RxDataStore<Preferences> dataStore;
if (dataStore == null) {dataStore = new RxPreferenceDataStoreBuilder(this, "ExoPlayerKeys").build();}
    @Overrideprotected 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)@Overridepublic 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() {@Overridepublic void onRenderedFirstFrame(EventTime eventTime, Object output, long renderTimeMs) {AnalyticsListener.super.onRenderedFirstFrame(eventTime, output, renderTimeMs);Log.i(TAG, "AnalyticsListener - onRenderedFirstFrame - 等待多长时间才能在屏幕上看到有意义的内容 - " + renderTimeMs);}@Overridepublic void onDroppedVideoFrames(EventTime eventTime, int droppedFrames, long elapsedMs) {AnalyticsListener.super.onDroppedVideoFrames(eventTime, droppedFrames, elapsedMs);Log.i(TAG, "AnalyticsListener - onDroppedVideoFrames - 视频丢帧 - " + droppedFrames);}@Overridepublic 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";@Overrideprotected 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());}@Overrideprotected 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;@Overridepublic void onCreate() {super.onCreate();ExoPlayer player = new ExoPlayer.Builder(this).build();mediaSession = new MediaSession.Builder(this, player).build();}@Nullable@Overridepublic MediaSession onGetSession(@NonNull ControllerInfo controllerInfo) {return mediaSession;}@Overridepublic void onTaskRemoved(Intent rootIntent) {super.onTaskRemoved(rootIntent);Player player = mediaSession.getPlayer();if (!player.getPlayWhenReady() || player.getMediaItemCount() == 0 || player.getPlaybackState() == Player.STATE_ENDED) {stopSelf();}}@Overridepublic 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@Overridepublic 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@Overridepublic 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) {@Overridepublic 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@Overridepublic ListenableFuture<LibraryResult<MediaItem>> onGetLibraryRoot(@NonNull MediaLibrarySession session, @NonNull MediaSession.ControllerInfo browser, @Nullable MediaLibraryService.LibraryParams params) {return MediaLibrarySession.Callback.super.onGetLibraryRoot(session, browser, params);}@NonNull@Overridepublic 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@Overridepublic 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@Overridepublic MediaLibrarySession onGetSession(@NonNull MediaSession.ControllerInfo controllerInfo) {return mediaLibrarySession;}@Overridepublic void onCreate() {super.onCreate();ExoPlayer player = new ExoPlayer.Builder(this).build();mediaLibrarySession = new MediaLibraryService.MediaLibrarySession.Builder(this, player, callback).build();}@Overridepublic 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@Overridepublic 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@Overridepublic 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" />
<serviceandroid: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@Overridepublic 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@Overridepublic 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@Overridepublic 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@Overridepublic MediaLibrarySession onGetSession(@NonNull MediaSession.ControllerInfo controllerInfo) {return mediaLibrarySession;}@Overridepublic void onCreate() {super.onCreate();ExoPlayer player = new ExoPlayer.Builder(this).build();mediaLibrarySession = new MediaLibraryService.MediaLibrarySession.Builder(this, player, callback).build();}@Overridepublic 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@Overridepublic 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
在这里插入图片描述

填坑中…

内容是真的多!


http://www.ppmy.cn/server/6299.html

相关文章

电脑工作者缓解眼部疲劳问题的工具分享

背景 作为以电脑为主要工作工具的人群&#xff0c;特别是开发人员&#xff0c;我们每天都需要长时间紧盯着屏幕&#xff0c;进行代码编写、程序调试、资料查询等工作。这种持续的工作模式无疑给我们的眼睛带来了不小的负担。一天下来&#xff0c;我们常常会感到眼睛干涩、疲劳…

浏览器工作原理与实践--HTTPS:让数据传输更安全

浏览器安全主要划分为三大块内容&#xff1a;页面安全、系统安全和网络安全。前面我们用四篇文章介绍了页面安全和系统安全&#xff0c;也聊了浏览器和Web开发者是如何应对各种类型的攻击&#xff0c;本文是我们专栏的最后一篇&#xff0c;我们就接着来聊聊网络安全协议HTTPS。…

Qt实现XYModem协议(四)

1 概述 XMODEM协议是一种使用拨号调制解调器的个人计算机通信中广泛使用的异步文件运输协议。这种协议以128字节块的形式传输数据&#xff0c;并且每个块都使用一个校验和过程来进行错误检测。使用循环冗余校验的与XMODEM相应的一种协议称为XMODEM-CRC。还有一种是XMODEM-1K&am…

网络安全(黑客技术)—2024自学

前言 什么是网络安全 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 如何成为一名黑客 很多朋友在学习安全方面都会半路转行&am…

matlab使用教程(46)—绘制条形图

1.条形图种类 如果需要查看一段时间内的结果、对比不同数据集的结果&#xff0c;或展示单个元素对汇总量的贡献和影响&#xff0c;则条形图会很有用处。 默认情况下&#xff0c;条形图会将一个向量或矩阵中的每个元素表现为一个条形&#xff0c;条形的高度与元素的值成比例。…

【MATLAB源码-第56期】基于WOA白鲸优化算法和PSO粒子群优化算法的三维路径规划对比。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 1.粒子群算法&#xff08;Particle Swarm Optimization&#xff0c;简称PSO&#xff09;是一种模拟鸟群觅食行为的启发式优化方法。以下是其详细描述&#xff1a; 基本思想&#xff1a; 鸟群在寻找食物时&#xff0c;每只鸟…

48.基于SpringBoot + Vue实现的前后端分离-雪具销售系统(项目 + 论文PPT)

项目介绍 本站是一个B/S模式系统&#xff0c;采用SpringBoot Vue框架&#xff0c;MYSQL数据库设计开发&#xff0c;充分保证系统的稳定性。系统具有界面清晰、操作简单&#xff0c;功能齐全的特点&#xff0c;使得基于SpringBoot Vue技术的雪具销售系统设计与实现管理工作系统…

案例分析-redis

案例需求&#xff1a;在7002这个slave节点执行手动故障转移&#xff0c;重新夺回master地位 步骤如下&#xff1a; 1&#xff09;利用redis-cli连接7002这个节点 2&#xff09;执行cluster failover命令 如图&#xff1a; 效果&#xff1a; 4.5.RedisTemplate访问分片集群 …