ToB企服应用市场:ToB评测及商务社交产业平台

标题: 【重生之我在学Android原生】Media3 [打印本页]

作者: 耶耶耶耶耶    时间: 2024-9-11 12:37
标题: 【重生之我在学Android原生】Media3
干系文章

【重生之我在学Android原生】ContentProvider(Java)
【重生之我在学Android原生】Media3
前言

内容颇多,尽量从简
ExoPlayer使用

官方文档
参考文章
实现效果

Android(java)
使用ExoPlayer播放视频,自定义ExoPlayer界面,记载播放位置(横屏竖屏切换/切换至后台等)
案例实现

创建项目



添加依赖


Sync 一下
  1. /// Jetpack Media3 ExoPlayer
  2.     implementation ("androidx.media3:media3-exoplayer:1.3.1")
  3.     implementation ("androidx.media3:media3-ui:1.3.1")
  4.     implementation ("androidx.media3:media3-common:1.3.1")
复制代码
转到activity_main.xml


选择Player.View
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.     xmlns:app="http://schemas.android.com/apk/res-auto"
  4.     xmlns:tools="http://schemas.android.com/tools"
  5.     android:layout_width="match_parent"
  6.     android:layout_height="match_parent"
  7.     tools:context=".MainActivity">
  8.     <androidx.media3.ui.PlayerView
  9.         android:id="@+id/video_view"
  10.         android:layout_width="match_parent"
  11.         android:layout_height="match_parent"
  12.         app:layout_constraintBottom_toBottomOf="parent"
  13.         app:layout_constraintEnd_toEndOf="parent"
  14.         app:layout_constraintStart_toStartOf="parent"
  15.         app:layout_constraintTop_toTopOf="parent" />
  16. </androidx.constraintlayout.widget.ConstraintLayout>
复制代码
player初始化

要在Activity的生命周期中完成player的初始化烧毁等


  1. package com.test.exoplayerexampleapplication;
  2. import androidx.annotation.OptIn;
  3. import androidx.appcompat.app.AppCompatActivity;
  4. import androidx.media3.common.util.UnstableApi;
  5. import androidx.media3.common.util.Util;
  6. import androidx.media3.ui.PlayerView;
  7. import android.os.Bundle;
  8. public class MainActivity extends AppCompatActivity {
  9.     @Override
  10.     protected void onCreate(Bundle savedInstanceState) {
  11.         super.onCreate(savedInstanceState);
  12.         setContentView(R.layout.activity_main);
  13.     }
  14.     @OptIn(markerClass = UnstableApi.class) @Override
  15.     protected void onStart() {
  16.         super.onStart();
  17.         if (Util.SDK_INT >= 24) {
  18.             initializePlayer();
  19.         }
  20.     }
  21.     @OptIn(markerClass = UnstableApi.class) @Override
  22.     protected void onResume() {
  23.         super.onResume();
  24.         if (Util.SDK_INT < 24) {
  25.             initializePlayer();
  26.         }
  27.     }
  28.     private void initializePlayer() {}
  29. }
复制代码
定义PlayerView,ExoPlayer

  1.     private PlayerView playerView;
  2.     private ExoPlayer exoPlayer;
  3.     private final Uri videoOneUri = Uri.parse("http://www.w3school.com.cn/example/html5/mov_bbb.mp4");
复制代码
在AndroidManifest.xml中定义网络权限,假如链接是Http,还需加userCleartextTraffic

  1. <uses-permission android:name="android.permission.INTERNET" />
复制代码
  1. android:usesCleartextTraffic="true"
复制代码
定义initializePlayer()方法
  1. private void initializePlayer() {
  2.         playerView = findViewById(R.id.video_view);
  3.         exoPlayer = new ExoPlayer.Builder(this).build();
  4.         playerView.setPlayer(exoPlayer);
  5.         MediaItem mediaItem = MediaItem.fromUri(videoOneUri);
  6.         exoPlayer.addMediaItem(mediaItem);
  7.         exoPlayer.prepare();
  8.     }
复制代码
运行可以播放视频

释放资源

媒体播放器是很占用资源的,所以当不再需要播放器时,要释放它,固然也要在Activity的生命周期中释放资源

在onStop和onPause适当调用

  1.     @OptIn(markerClass = UnstableApi.class) @Override
  2.     protected void onPause() {
  3.         super.onPause();
  4.         if (Util.SDK_INT < 24) {
  5.             releasePlayer();
  6.         }
  7.     }
  8.     @OptIn(markerClass = UnstableApi.class) @Override
  9.     protected void onStop() {
  10.         super.onStop();
  11.         if (Util.SDK_INT >= 24) {
  12.             releasePlayer();
  13.         }
  14.     }
  15.     private void releasePlayer() {
  16.         exoPlayer.release();
  17.     }
复制代码
记载视频的播放状态

重新运行项目,测试切换至后台

切换至后台的生命周期,可以知道当APP放到后台,会调用player.release()方法释放

我们发现视频切换到后台,再切换回来,之前的播放到第四秒,切换回来发现,又回到第0秒。所以,现在需要记载之前播放的位置,播放的状态,播放到第几个视频了。
既然没有onDestory掉Activity
那么定义三个变量(播放位置,播放状态,第几个视频)
  1.     private Long playbackPosition = 0L;
  2.     private int currentMediaItemIndex = 0;
  3.     private boolean playWhenReady = false;
复制代码

  1. playbackPosition = exoPlayer.getCurrentPosition();
  2.         currentMediaItemIndex = exoPlayer.getCurrentMediaItemIndex();
  3.         playWhenReady = exoPlayer.getPlayWhenReady();
复制代码
  1. exoPlayer.setPlayWhenReady(playWhenReady);
  2.         exoPlayer.seekTo(currentMediaItemIndex, playbackPosition);
复制代码
添加新的视频链接
  1. private final Uri videoTwoUri = Uri.parse("https://media.w3.org/2010/05/sintel/trailer.mp4");
复制代码

运行测试,播放下一个视频,并播放至一半,切换到后台,再切换回来

横竖屏切换


横竖屏切换的生命周期,Activity被Destory了

使用SharedPreference来存储键值对,参考官方文档,官方推荐用DataStore

那使用DataStore

我们现在做的事情是,使用DataStore Preference来存储视频在释放之前的播放进度,播放状态,播放到第几个视频了。
按照官网设置Preferences DataStore
引入依赖

  1. /// Preferences DataStore
  2.     implementation("androidx.datastore:datastore-preferences:1.0.0")
  3.     /// RxJava2 support
  4.     implementation("androidx.datastore:datastore-preferences-rxjava2:1.0.0")
  5.     /// RxJava3 support
  6.     implementation("androidx.datastore:datastore-preferences-rxjava3:1.0.0")
复制代码
实例RxDataStore,烧毁
  1. private RxDataStore<Preferences> dataStore;
复制代码
  1. if (dataStore == null) {
  2.             dataStore = new RxPreferenceDataStoreBuilder(this, "ExoPlayerKeys").build();
  3.         }
复制代码
  1.     @Override
  2.     protected void onDestroy() {
  3.         super.onDestroy();
  4.         if (dataStore != null) {
  5.             dataStore.dispose();
  6.         }
  7.     }
复制代码


定义三个Key
  1.     Preferences.Key<Integer> currentMediaItemIndexPK;
  2.     Preferences.Key<Integer> playbackPositionPK;
  3.     Preferences.Key<Boolean> playWhenReadyPK;
复制代码
  1. currentMediaItemIndexPK = PreferencesKeys.intKey("currentMediaItemIndex");
  2.         playbackPositionPK = PreferencesKeys.intKey("playbackPosition");
  3.         playWhenReadyPK = PreferencesKeys.booleanKey("playWhenReady");
复制代码

使用PreferencesKeys存储值

  1. private void saveVideoRecord() {
  2.         dataStore.updateDataAsync(preferences -> {
  3.             MutablePreferences mutablePreferences = preferences.toMutablePreferences();
  4.             mutablePreferences.set(currentMediaItemIndexPK, currentMediaItemIndex);
  5.             mutablePreferences.set(playbackPositionPK, playbackPosition.intValue());
  6.             mutablePreferences.set(playWhenReadyPK, playWhenReady);
  7.             return Single.just(mutablePreferences);
  8.         });
  9.     }
复制代码
在onCreate()中取出之前存储的值,首次运行时,是没有存值的,所以会异常NULL,所以没有值就初始化这些变量

  1. try {
  2.             currentMediaItemIndex = dataStore.data().map(preferences -> preferences.get(currentMediaItemIndexPK)).blockingFirst();
  3.         } catch (Exception e) {
  4.             currentMediaItemIndex = 0;
  5.         }
  6.         try {
  7.             playbackPosition = Long.valueOf(dataStore.data().map(preferences -> preferences.get(playbackPositionPK)).blockingFirst());
  8.         } catch (Exception e) {
  9.             playbackPosition = 0L;
  10.         }
  11.         try {
  12.             playWhenReady = dataStore.data().map(preferences -> preferences.get(playWhenReadyPK)).blockingFirst();
  13.         } catch (Exception e) {
  14.             playWhenReady = false;
  15.         }
复制代码
删除APP,重新运行项目,旋转屏幕也能记载播放状态,和播放进度,播放第几个视频


旋转后,仍然从之前播放的进度继续,保持停息。
监听事件

参考链接
  1. @OptIn(markerClass = UnstableApi.class)
  2.     private void addPlayerListener() {
  3.         listener = new Player.Listener() {
  4.             @OptIn(markerClass = UnstableApi.class)
  5.             @Override
  6.             public void onPlaybackStateChanged(int playbackState) {
  7.                 Player.Listener.super.onPlaybackStateChanged(playbackState);
  8.                 String stateString = "UNKNOWN_STATE";
  9.                 if (playbackState == ExoPlayer.STATE_IDLE) {
  10.                     stateString = "EXoPlayer.STATE_IDLE";
  11.                 } else if (playbackState == ExoPlayer.STATE_BUFFERING) {
  12.                     stateString = "ExoPlayer.STATE_BUFFERING";
  13.                 } else if (playbackState == Player.STATE_READY) {
  14.                     stateString = "ExoPlayer.STATE_READY";
  15.                 } else if (playbackState == Player.STATE_ENDED) {
  16.                     stateString = "ExoPlayer.STATE_ENDED";
  17.                 }
  18.                 Log.d(TAG, "changed state to " + stateString);
  19.             }
  20.         };
  21.         exoPlayer.addListener(listener);
  22.         analyticsListener = new AnalyticsListener() {
  23.             @Override
  24.             public void onRenderedFirstFrame(EventTime eventTime, Object output, long renderTimeMs) {
  25.                 AnalyticsListener.super.onRenderedFirstFrame(eventTime, output, renderTimeMs);
  26.                 Log.i(TAG, "AnalyticsListener - onRenderedFirstFrame - 等待多长时间才能在屏幕上看到有意义的内容 - " + renderTimeMs);
  27.             }
  28.             @Override
  29.             public void onDroppedVideoFrames(EventTime eventTime, int droppedFrames, long elapsedMs) {
  30.                 AnalyticsListener.super.onDroppedVideoFrames(eventTime, droppedFrames, elapsedMs);
  31.                 Log.i(TAG, "AnalyticsListener - onDroppedVideoFrames - 视频丢帧 - " + droppedFrames);
  32.             }
  33.             @Override
  34.             public void onAudioUnderrun(EventTime eventTime, int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
  35.                 AnalyticsListener.super.onAudioUnderrun(eventTime, bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
  36.                 Log.i(TAG, "AnalyticsListener - onAudioUnderrun - 音频欠载 - " + bufferSizeMs);
  37.             }
  38.         };
  39.         exoPlayer.addAnalyticsListener(analyticsListener);
  40.     }
复制代码
自定义ExoPlayer界面

按住Command键,左键查看PlayerView.java,鼠标hover在文件上,看它的位置,可以顺藤摸瓜找到它的源码

布局界面在这里

复制到本身的项目中

在此基础上更改custom_player_control_view.xml


MediaSessionService后台播放

参考文章
实现效果

让媒体挂在后台播放,如下图

正如官网所说,可以用在长视频,听视频。显然上面没有许多的交互,假如短视频用这个,既看不到播放的内容,也没啥交互,就还是不用MediaSession

案例实现

按照官方设置的步调,语言还是采用Java。将构建一个MediaSession,让他在后台一直播放,同时可以自定义几个按钮,如点赞,收藏。末了监听视频是否播放。
MediaController

显然需要用到MediaSessionService和MediaController


创建项目



按照步调,先new 一个ListenableFuture controllerFuture
先引入依赖


  1.     implementation ("androidx.media3:media3-session:1.3.1")
  2.     implementation ("androidx.media3:media3-exoplayer:1.3.1")
  3.     implementation ("androidx.media3:media3-ui:1.3.1")
  4.     implementation ("androidx.media3:media3-common:1.3.1")
复制代码

定义MediaController


  1. package com.test.mediasessionserviceexample;
  2. import androidx.appcompat.app.AppCompatActivity;
  3. import androidx.media3.common.MediaItem;
  4. import androidx.media3.common.MediaMetadata;
  5. import androidx.media3.session.MediaController;
  6. import androidx.media3.session.SessionToken;
  7. import android.content.ComponentName;
  8. import android.net.Uri;
  9. import android.os.Bundle;
  10. import com.google.common.util.concurrent.ListenableFuture;
  11. import com.google.common.util.concurrent.MoreExecutors;
  12. import java.util.concurrent.ExecutionException;
  13. public class MainActivity extends AppCompatActivity {
  14.     ListenableFuture<MediaController> controllerFuture;
  15.     private final String mediaUrl = "https://media.w3.org/2010/05/sintel/trailer.mp4";
  16.     @Override
  17.     protected void onCreate(Bundle savedInstanceState) {
  18.         super.onCreate(savedInstanceState);
  19.         setContentView(R.layout.activity_main);
  20.         SessionToken sessionToken = new SessionToken(this, new ComponentName(this, 你的服务));
  21.         controllerFuture = new MediaController.Builder(this, sessionToken).buildAsync();
  22.         controllerFuture.addListener(() -> {
  23.             Uri uri = Uri.parse(mediaUrl);
  24.             MediaMetadata metadata = new MediaMetadata.Builder()
  25.                     .setArtist("阿笙")
  26.                     .setTitle("我名字是视频")
  27.                     .build();
  28.             MediaItem mediaItem = new MediaItem.Builder()
  29.                     .setMediaId("media-one")
  30.                     .setUri(uri)
  31.                     .setMediaMetadata(metadata).build();
  32.             try {
  33.                 MediaController mediaController = controllerFuture.get();
  34.                 mediaController.setMediaItem(mediaItem);
  35.                 mediaController.prepare();
  36.             } catch (ExecutionException | InterruptedException e) {
  37.                 throw new RuntimeException(e);
  38.             }
  39.         }, MoreExecutors.directExecutor());
  40.     }
  41.     @Override
  42.     protected void onStop() {
  43.         super.onStop();
  44.         MediaController.releaseFuture(controllerFuture);
  45.     }
  46. }
复制代码
定义服务MediaSessionService


  1. package com.test.mediasessionserviceexample;
  2. import android.content.Intent;
  3. import androidx.annotation.NonNull;
  4. import androidx.annotation.Nullable;
  5. import androidx.media3.common.Player;
  6. import androidx.media3.exoplayer.ExoPlayer;
  7. import androidx.media3.session.MediaSession;
  8. import androidx.media3.session.MediaSession.ControllerInfo;
  9. import androidx.media3.session.MediaSessionService;
  10. public class PlaybackService extends MediaSessionService {
  11.     private MediaSession mediaSession = null;
  12.     @Override
  13.     public void onCreate() {
  14.         super.onCreate();
  15.         ExoPlayer player = new ExoPlayer.Builder(this).build();
  16.         mediaSession = new MediaSession.Builder(this, player).build();
  17.     }
  18.     @Nullable
  19.     @Override
  20.     public MediaSession onGetSession(@NonNull ControllerInfo controllerInfo) {
  21.         return mediaSession;
  22.     }
  23.     @Override
  24.     public void onTaskRemoved(Intent rootIntent) {
  25.         super.onTaskRemoved(rootIntent);
  26.         Player player = mediaSession.getPlayer();
  27.         if (!player.getPlayWhenReady() || player.getMediaItemCount() == 0 || player.getPlaybackState() == Player.STATE_ENDED) {
  28.             stopSelf();
  29.         }
  30.     }
  31.     @Override
  32.     public void onDestroy() {
  33.         super.onDestroy();
  34.         mediaSession.getPlayer().release();
  35.         mediaSession.release();
  36.         mediaSession = null;
  37.     }
  38. }
复制代码

注册服务/申明服务
  1. <service android:name=".PlaybackService"
  2.             android:foregroundServiceType="mediaPlayback"
  3.             android:exported="true">
  4.             <intent-filter>
  5.                 <action android:name="androidx.media3.session.MediaSessionService"/>
  6.             </intent-filter>
  7.         </service>
复制代码
申明权限(网络、前台服务)
  1. <uses-permission android:name="android.permission.INTERNET" />
  2.     <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
  3.     <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
复制代码

运行项目

下拉通知,可以看到这个服务,

定义按钮CommandButton

引入图标


  1.     private CommandButton cBRemoveFromLikes;
  2.     private CommandButton cBRemoveFromFavorites;
  3.     private CommandButton cBAddToLikes;
  4.     private CommandButton cBAddToFavorites;
  5.     private SessionCommand sCAddToLikes;
  6.     private SessionCommand sCAddToFavorites;
  7.     private SessionCommand sCRemoveFromLike;
  8.     private SessionCommand sCRemoveFromFavorite;
  9.     private static final String SAVE_TO_LIKES = "save to likes";
  10.     private static final String SAVE_TO_FAVORITES = "save to favorites";
  11.     private static final String REMOVE_FROM_LIKES = "remove from likes";
  12.     private static final String REMOVE_FROM_FAVORITES = "remove from favorites";
复制代码
  1. sCAddToLikes = new SessionCommand(SAVE_TO_LIKES, new Bundle());
  2.         sCAddToFavorites = new SessionCommand(SAVE_TO_FAVORITES, new Bundle());
  3.         sCRemoveFromLike = new SessionCommand(REMOVE_FROM_LIKES, new Bundle());
  4.         sCRemoveFromFavorite = new SessionCommand(REMOVE_FROM_FAVORITES, new Bundle());
  5.         cBAddToLikes = buildCommandButton(SAVE_TO_LIKES, R.drawable.like_icon, sCAddToLikes);
  6.         cBAddToFavorites = buildCommandButton(SAVE_TO_FAVORITES, R.drawable.favorite_icon, sCAddToFavorites);
  7.         cBRemoveFromLikes = buildCommandButton(REMOVE_FROM_LIKES, R.drawable.like_remove_icon, sCRemoveFromLike);
  8.         cBRemoveFromFavorites = buildCommandButton(REMOVE_FROM_FAVORITES, R.drawable.favorite_remove_icon, sCRemoveFromFavorite);
复制代码
  1. private CommandButton buildCommandButton(String displayName, int iconResId, SessionCommand sessionCommand) {
  2.         return new CommandButton.Builder()
  3.                 .setDisplayName(displayName)
  4.                 .setIconResId(iconResId)
  5.                 .setSessionCommand(sessionCommand)
  6.                 .build();
  7.     }
复制代码
自定义MediaSession的布局


定义MediaSession可以使用哪些自定义命令
在MediaSession.Builder()中setCallback 回调,setCustomLayout布局

通过吸收customAction的值,判断当前是什么命令,对应设置布局

  1. class CustomMediaSessionCallback implements MediaSession.Callback {
  2.         @OptIn(markerClass = UnstableApi.class)
  3.         @NonNull
  4.         @Override
  5.         public ConnectionResult onConnect(@NonNull MediaSession session, @NonNull ControllerInfo controller) {
  6.             SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
  7.                     .add(sCAddToLikes)
  8.                     .add(sCAddToFavorites)
  9.                     .add(sCRemoveFromLike)
  10.                     .add(sCRemoveFromFavorite)
  11.                     .build();
  12.             return new ConnectionResult.AcceptedResultBuilder(session)
  13.                     .setAvailableSessionCommands(sessionCommands)
  14.                     .build();
  15.         }
  16.         @OptIn(markerClass = UnstableApi.class)
  17.         @NonNull
  18.         @Override
  19.         public ListenableFuture<SessionResult> onCustomCommand(@NonNull MediaSession session, @NonNull ControllerInfo controller, @NonNull SessionCommand customCommand, @NonNull Bundle args) {
  20.             final CommandButton buttonOne = mediaSession.getCustomLayout().get(0);
  21.             final CommandButton buttonTwo = mediaSession.getCustomLayout().get(1);
  22.             switch (customCommand.customAction) {
  23.                 case SAVE_TO_LIKES:
  24.                     mediaSession.setCustomLayout(ImmutableList.of(cBRemoveFromLikes, buttonTwo));
  25.                     break;
  26.                 case SAVE_TO_FAVORITES:
  27.                     mediaSession.setCustomLayout(ImmutableList.of(buttonOne, cBRemoveFromFavorites));
  28.                     break;
  29.                 case REMOVE_FROM_LIKES:
  30.                     mediaSession.setCustomLayout(ImmutableList.of(cBAddToLikes, buttonTwo));
  31.                     break;
  32.                 case REMOVE_FROM_FAVORITES:
  33.                     mediaSession.setCustomLayout(ImmutableList.of(buttonOne, cBAddToFavorites));
  34.                     break;
  35.                 default:
  36.                     throw new RuntimeException("not implement");
  37.             }
  38.             return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS));
  39.         }
  40.     }
复制代码
运行测试


自定义Player的行为(play等)


  1.         ExoPlayer player = new ExoPlayer.Builder(this).build();
  2.         ForwardingPlayer forwardingPlayer = new ForwardingPlayer(player) {
  3.             @Override
  4.             public void play() {
  5.                 super.play();
  6.                 Log.d("ForwardingPlayer Log", "play!");
  7.             }
  8.         };
  9.         mediaSession = new MediaSession.Builder(this, forwardingPlayer)
  10.                 .setCallback(new CustomMediaSessionCallback())
  11.                 .setCustomLayout(ImmutableList.of(cBAddToLikes, cBAddToFavorites))
  12.                 .build();
复制代码
MediaLibraryService

实现目标

参考文档
主要实现三个函数

实现效果

创建一个目录,目录下有三个文件,并且可以根据标题搜索这三个文件

案例实现

采用JAVA实现
创建项目接着引入依赖


  1.     implementation ("androidx.media3:media3-session:1.3.1")
  2.     implementation ("androidx.media3:media3-exoplayer:1.3.1")
  3.     implementation ("androidx.media3:media3-ui:1.3.1")
  4.     implementation ("androidx.media3:media3-common:1.3.1")
复制代码
初始化MediaLibraryService



和MediaSession利用差不多,先实例

  1. package com.test.medialibraryservicetestapplication;
  2. import androidx.annotation.NonNull;
  3. import androidx.annotation.Nullable;
  4. import androidx.media3.common.MediaItem;
  5. import androidx.media3.exoplayer.ExoPlayer;
  6. import androidx.media3.session.LibraryResult;
  7. import androidx.media3.session.MediaLibraryService;
  8. import androidx.media3.session.MediaSession;
  9. import com.google.common.collect.ImmutableList;
  10. import com.google.common.util.concurrent.ListenableFuture;
  11. public class PlaybackService extends MediaLibraryService {
  12.     MediaLibrarySession mediaLibrarySession = null;
  13.     MediaLibrarySession.Callback callback = new MediaLibrarySession.Callback() {
  14.         @NonNull
  15.         @Override
  16.         public ListenableFuture<LibraryResult<MediaItem>> onGetLibraryRoot(@NonNull MediaLibrarySession session, @NonNull MediaSession.ControllerInfo browser, @Nullable MediaLibraryService.LibraryParams params) {
  17.             return MediaLibrarySession.Callback.super.onGetLibraryRoot(session, browser, params);
  18.         }
  19.         @NonNull
  20.         @Override
  21.         public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> onGetChildren(MediaLibrarySession session, @NonNull MediaSession.ControllerInfo browser, @NonNull String parentId, int page, int pageSize, @Nullable MediaLibraryService.LibraryParams params) {
  22.             return MediaLibrarySession.Callback.super.onGetChildren(session, browser, parentId, page, pageSize, params);
  23.         }
  24.         @NonNull
  25.         @Override
  26.         public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> onGetSearchResult(@NonNull MediaLibrarySession session, @NonNull MediaSession.ControllerInfo browser, @NonNull String query, int page, int pageSize, @Nullable MediaLibraryService.LibraryParams params) {
  27.             return MediaLibrarySession.Callback.super.onGetSearchResult(session, browser, query, page, pageSize, params);
  28.         }
  29.     };
  30.     @Nullable
  31.     @Override
  32.     public MediaLibrarySession onGetSession(@NonNull MediaSession.ControllerInfo controllerInfo) {
  33.         return mediaLibrarySession;
  34.     }
  35.     @Override
  36.     public void onCreate() {
  37.         super.onCreate();
  38.         ExoPlayer player = new ExoPlayer.Builder(this).build();
  39.         mediaLibrarySession = new MediaLibraryService.MediaLibrarySession.Builder(this, player, callback).build();
  40.     }
  41.     @Override
  42.     public void onDestroy() {
  43.         super.onDestroy();
  44.         if (mediaLibrarySession != null) {
  45.             mediaLibrarySession.getPlayer().release();
  46.             mediaLibrarySession.release();
  47.             mediaLibrarySession = null;
  48.         }
  49.     }
  50. }
复制代码
创建树形结构存储数据

要获取根对象,需要有一个建好的树形数据,就如下面这幅图上,它是一个树,树的根节点RootNode,分叉出去好几个类目,简单起见,这里先弄简单的Root->Music/Game/Study

定义树

构建根节点,添加三个子节点到根节点

末了改为单例
  1. package com.test.medialibraryservicetestapplication;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. public class Tree {
  5.     Map<String, TreeNode> treeNodes;
  6.     final String ROOT_ID = "root";
  7.     final String MUSIC_ID = "music";
  8.     final String GAME_ID = "game";
  9.     final String STUDY_ID = "study";
  10.     final String ROOT_TITLE = "root title";
  11.     final String MUSIC_TITLE = "music title";
  12.     final String GAME_TITLE = "game title";
  13.     final String STUDY_TITLE = "study title";
  14.     private final static Tree instance = new Tree();
  15.     public static Tree genInstance() {
  16.         return instance;
  17.     }
  18.     private boolean isInitialized = false;
  19.     private Tree() {
  20.         if (!isInitialized) {
  21.             isInitialized = true;
  22.             treeNodes = new HashMap<>();
  23.             TreeNode rootNode = new TreeNode(ROOT_ID, ROOT_TITLE);
  24.             TreeNode musicNode = new TreeNode(MUSIC_ID, MUSIC_TITLE);
  25.             TreeNode gameNode = new TreeNode(GAME_ID, GAME_TITLE);
  26.             TreeNode studyNode = new TreeNode(STUDY_ID, STUDY_TITLE);
  27.             rootNode.children.add(musicNode.node);
  28.             rootNode.children.add(gameNode.node);
  29.             rootNode.children.add(studyNode.node);
  30.             treeNodes.put(ROOT_ID, rootNode);
  31.         }
  32.     }
  33. }
复制代码
获取根节点


  1. MediaItem getTreeRoot() {
  2.         return Objects.requireNonNull(treeNodes.get(ROOT_ID)).node;
  3.     }
复制代码
  1.         @NonNull
  2.         @Override
  3.         public ListenableFuture<LibraryResult<MediaItem>> onGetLibraryRoot(@NonNull MediaLibrarySession session, @NonNull MediaSession.ControllerInfo browser, @Nullable MediaLibraryService.LibraryParams params) {
  4.             return Futures.immediateFuture(LibraryResult.ofItem(Tree.genInstance().getTreeRoot(), params));
  5.         }
复制代码
获取结点的子节点集合


  1.     List<MediaItem> getNodeChildren(String treeNodeId) {
  2.         return Objects.requireNonNull(treeNodes.get(treeNodeId)).children;
  3.     }
复制代码
  1. @NonNull
  2.         @Override
  3.         public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> onGetChildren(MediaLibrarySession session, @NonNull MediaSession.ControllerInfo browser, @NonNull String parentId, int page, int pageSize, @Nullable MediaLibraryService.LibraryParams params) {
  4.             List<MediaItem> children = Tree.genInstance().getNodeChildren(parentId);
  5.             if (!children.isEmpty()) {
  6.                 return Futures.immediateFuture(LibraryResult.ofItemList(children, params));
  7.             }
  8.             return Futures.immediateFuture(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
  9.         }
复制代码
注册Service及申明权限


  1. <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
  2.     <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
复制代码
  1. <service
  2.             android:name=".PlaybackService"
  3.             android:exported="true"
  4.             android:foregroundServiceType="mediaPlayback">
  5.             <intent-filter>
  6.                 <action android:name="androidx.media3.session.MediaSessionService" />
  7.             </intent-filter>
  8.         </service>
复制代码
MediaBrowser浏览MediaLibraryService定义的媒体库



获取MediaLibraryService构建的媒体库的根节点

获取某一节点下的子节点

运行测试

运行项目,并在60行打上断点,看到value属性值为三个MediaItem对象
可以看到获取到根节点下的三个子节点

  1. 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
  2.         @Override
  3.         public ListenableFuture<LibraryResult<MediaItem>> onGetLibraryRoot(@NonNull MediaLibrarySession session, @NonNull MediaSession.ControllerInfo browser, @Nullable MediaLibraryService.LibraryParams params) {
  4.             return Futures.immediateFuture(LibraryResult.ofItem(Tree.genInstance().getTreeRoot(), params));
  5.         }
  6.         @NonNull
  7.         @Override
  8.         public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> onGetChildren(MediaLibrarySession session, @NonNull MediaSession.ControllerInfo browser, @NonNull String parentId, int page, int pageSize, @Nullable MediaLibraryService.LibraryParams params) {
  9.             List<MediaItem> children = Tree.genInstance().getNodeChildren(parentId);
  10.             if (!children.isEmpty()) {
  11.                 return Futures.immediateFuture(LibraryResult.ofItemList(children, params));
  12.             }
  13.             return Futures.immediateFuture(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
  14.         }
  15.         @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中定义搜索的函数

  1.     List<MediaItem> search(String query) {
  2.         List<MediaItem> titleMatches = new ArrayList<>();
  3.         Object[] words = Arrays.stream(query.split(" ")).map(it -> it.trim().toLowerCase()).filter(it -> it.length() > 1).toArray();
  4.         titleNodes.keySet().forEach(title -> {
  5.             TreeNode treeNode = titleNodes.get(title);
  6.             for (Object word : words) {
  7.                 boolean contains = title.contains((CharSequence) word);
  8.                 if (contains) {
  9.                     assert treeNode != null;
  10.                     titleMatches.add(treeNode.node);
  11.                 }
  12.             }
  13.         });
  14.         return titleMatches;
  15.     }
复制代码
  1.     public static Map<String, TreeNode> titleNodes;
  2. titleNodes = new HashMap<>();
  3.             titleNodes.put(MUSIC_TITLE, musicNode);
  4.             titleNodes.put(GAME_TITLE, gameNode);
  5.             titleNodes.put(STUDY_TITLE, studyNode);
复制代码

  1.         @NonNull
  2.         @Override
  3.         public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> onGetSearchResult(@NonNull MediaLibrarySession session, @NonNull MediaSession.ControllerInfo browser, @NonNull String query, int page, int pageSize, @Nullable MediaLibraryService.LibraryParams params) {
  4.             return Futures.immediateFuture(LibraryResult.ofItemList(Tree.genInstance().search(query), params));
  5.         }
复制代码
MediaBrowser 搜索

  1.     private void searchMedia(String query) {
  2.         ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> searchFuture = mediaBrowser.getSearchResult(query, 0, Integer.MAX_VALUE, null);
  3.         searchFuture.addListener(() -> {
  4.             try {
  5.                 LibraryResult<ImmutableList<MediaItem>> immutableListLibraryResult = searchFuture.get();
  6.                 ImmutableList<MediaItem> value = immutableListLibraryResult.value;
  7.             } catch (ExecutionException | InterruptedException e) {
  8.                 throw new RuntimeException(e);
  9.             }
  10.         }, MoreExecutors.directExecutor());
  11.     }
复制代码

搜索“ga”,可以查到“game”这个MediaItem

填坑中…

内容是真的多!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4