26
26
import static androidx .media3 .session .MediaUtils .TRANSACTION_SIZE_LIMIT_IN_BYTES ;
27
27
28
28
import android .annotation .SuppressLint ;
29
+ import android .graphics .Bitmap ;
29
30
import android .os .BadParcelableException ;
30
31
import android .os .Bundle ;
31
32
import android .os .RemoteException ;
37
38
import androidx .media .MediaBrowserServiceCompat ;
38
39
import androidx .media .MediaSessionManager .RemoteUserInfo ;
39
40
import androidx .media3 .common .MediaItem ;
41
+ import androidx .media3 .common .MediaMetadata ;
40
42
import androidx .media3 .common .util .ConditionVariable ;
41
43
import androidx .media3 .common .util .Log ;
42
44
import androidx .media3 .common .util .Util ;
43
45
import androidx .media3 .session .MediaLibraryService .LibraryParams ;
44
46
import androidx .media3 .session .MediaSession .ControllerCb ;
45
47
import androidx .media3 .session .MediaSession .ControllerInfo ;
46
48
import com .google .common .collect .ImmutableList ;
49
+ import com .google .common .util .concurrent .AsyncFunction ;
50
+ import com .google .common .util .concurrent .Futures ;
47
51
import com .google .common .util .concurrent .ListenableFuture ;
48
52
import com .google .common .util .concurrent .MoreExecutors ;
53
+ import com .google .common .util .concurrent .SettableFuture ;
49
54
import java .util .ArrayList ;
50
55
import java .util .List ;
51
56
import java .util .concurrent .CancellationException ;
52
57
import java .util .concurrent .ExecutionException ;
53
58
import java .util .concurrent .Future ;
59
+ import java .util .concurrent .atomic .AtomicInteger ;
54
60
import java .util .concurrent .atomic .AtomicReference ;
61
+ import org .checkerframework .checker .nullness .compatqual .NullableType ;
55
62
56
63
/**
57
64
* Implementation of {@link MediaBrowserServiceCompat} for interoperability between {@link
@@ -218,7 +225,11 @@ public void onLoadChildren(
218
225
ListenableFuture <LibraryResult <ImmutableList <MediaItem >>> future =
219
226
librarySessionImpl .onGetChildrenOnHandler (
220
227
controller , parentId , page , pageSize , params );
221
- sendLibraryResultWithMediaItemsWhenReady (result , future );
228
+ ListenableFuture <@ NullableType List <MediaBrowserCompat .MediaItem >>
229
+ browserItemsFuture =
230
+ Util .transformFutureAsync (
231
+ future , createMediaItemsToBrowserItemsAsyncFunction ());
232
+ sendLibraryResultWithMediaItemsWhenReady (result , browserItemsFuture );
222
233
return ;
223
234
}
224
235
// Cannot distinguish onLoadChildren() why it's called either by
@@ -236,7 +247,9 @@ public void onLoadChildren(
236
247
/* page= */ 0 ,
237
248
/* pageSize= */ Integer .MAX_VALUE ,
238
249
/* params= */ null );
239
- sendLibraryResultWithMediaItemsWhenReady (result , future );
250
+ ListenableFuture <@ NullableType List <MediaBrowserCompat .MediaItem >> browserItemsFuture =
251
+ Util .transformFutureAsync (future , createMediaItemsToBrowserItemsAsyncFunction ());
252
+ sendLibraryResultWithMediaItemsWhenReady (result , browserItemsFuture );
240
253
});
241
254
}
242
255
@@ -264,7 +277,9 @@ public void onLoadItem(String itemId, Result<MediaBrowserCompat.MediaItem> resul
264
277
}
265
278
ListenableFuture <LibraryResult <MediaItem >> future =
266
279
librarySessionImpl .onGetItemOnHandler (controller , itemId );
267
- sendLibraryResultWithMediaItemWhenReady (result , future );
280
+ ListenableFuture <MediaBrowserCompat .@ NullableType MediaItem > browserItemFuture =
281
+ Util .transformFutureAsync (future , createMediaItemToBrowserItemAsyncFunction ());
282
+ sendLibraryResultWithMediaItemWhenReady (result , browserItemFuture );
268
283
});
269
284
}
270
285
@@ -362,17 +377,12 @@ private static void sendCustomActionResultWhenReady(
362
377
363
378
private static void sendLibraryResultWithMediaItemWhenReady (
364
379
Result <MediaBrowserCompat .MediaItem > result ,
365
- ListenableFuture <LibraryResult < MediaItem > > future ) {
380
+ ListenableFuture <MediaBrowserCompat . @ NullableType MediaItem > future ) {
366
381
future .addListener (
367
382
() -> {
368
383
try {
369
- LibraryResult <MediaItem > libraryResult =
370
- checkNotNull (future .get (), "LibraryResult must not be null" );
371
- if (libraryResult .resultCode != RESULT_SUCCESS || libraryResult .value == null ) {
372
- result .sendResult (/* result= */ null );
373
- } else {
374
- result .sendResult (MediaUtils .convertToBrowserItem (libraryResult .value ));
375
- }
384
+ MediaBrowserCompat .MediaItem mediaItem = future .get ();
385
+ result .sendResult (mediaItem );
376
386
} catch (CancellationException | ExecutionException | InterruptedException unused ) {
377
387
result .sendError (/* extras= */ null );
378
388
}
@@ -382,27 +392,146 @@ private static void sendLibraryResultWithMediaItemWhenReady(
382
392
383
393
private static void sendLibraryResultWithMediaItemsWhenReady (
384
394
Result <List <MediaBrowserCompat .MediaItem >> result ,
385
- ListenableFuture <LibraryResult < ImmutableList < MediaItem > >> future ) {
395
+ ListenableFuture <@ NullableType List < MediaBrowserCompat . MediaItem >> future ) {
386
396
future .addListener (
387
397
() -> {
388
398
try {
389
- LibraryResult <ImmutableList <MediaItem >> libraryResult =
390
- checkNotNull (future .get (), "LibraryResult must not be null" );
391
- if (libraryResult .resultCode != RESULT_SUCCESS || libraryResult .value == null ) {
392
- result .sendResult (/* result= */ null );
393
- } else {
394
- result .sendResult (
395
- MediaUtils .truncateListBySize (
396
- MediaUtils .convertToBrowserItemList (libraryResult .value ),
397
- TRANSACTION_SIZE_LIMIT_IN_BYTES ));
398
- }
399
+ List <MediaBrowserCompat .MediaItem > mediaItems = future .get ();
400
+ result .sendResult (
401
+ (mediaItems == null )
402
+ ? null
403
+ : MediaUtils .truncateListBySize (mediaItems , TRANSACTION_SIZE_LIMIT_IN_BYTES ));
399
404
} catch (CancellationException | ExecutionException | InterruptedException unused ) {
400
405
result .sendError (/* extras= */ null );
401
406
}
402
407
},
403
408
MoreExecutors .directExecutor ());
404
409
}
405
410
411
+ private AsyncFunction <
412
+ LibraryResult <ImmutableList <MediaItem >>, @ NullableType List <MediaBrowserCompat .MediaItem >>
413
+ createMediaItemsToBrowserItemsAsyncFunction () {
414
+ return result -> {
415
+ checkNotNull (result , "LibraryResult must not be null" );
416
+ SettableFuture <@ NullableType List <MediaBrowserCompat .MediaItem >> outputFuture =
417
+ SettableFuture .create ();
418
+ if (result .resultCode != RESULT_SUCCESS || result .value == null ) {
419
+ outputFuture .set (null );
420
+ return outputFuture ;
421
+ }
422
+
423
+ ImmutableList <MediaItem > mediaItems = result .value ;
424
+ if (mediaItems .isEmpty ()) {
425
+ outputFuture .set (new ArrayList <>());
426
+ return outputFuture ;
427
+ }
428
+
429
+ List <@ NullableType ListenableFuture <Bitmap >> bitmapFutures = new ArrayList <>();
430
+ outputFuture .addListener (
431
+ () -> {
432
+ if (outputFuture .isCancelled ()) {
433
+ cancelAllFutures (bitmapFutures );
434
+ }
435
+ },
436
+ MoreExecutors .directExecutor ());
437
+
438
+ final AtomicInteger resultCount = new AtomicInteger (0 );
439
+ Runnable handleBitmapFuturesTask =
440
+ () -> {
441
+ int completedBitmapFutureCount = resultCount .incrementAndGet ();
442
+ if (completedBitmapFutureCount == mediaItems .size ()) {
443
+ handleBitmapFuturesAllCompletedAndSetOutputFuture (
444
+ bitmapFutures , mediaItems , outputFuture );
445
+ }
446
+ };
447
+
448
+ for (int i = 0 ; i < mediaItems .size (); i ++) {
449
+ MediaItem mediaItem = mediaItems .get (i );
450
+ MediaMetadata metadata = mediaItem .mediaMetadata ;
451
+ if (metadata .artworkData == null ) {
452
+ bitmapFutures .add (null );
453
+ handleBitmapFuturesTask .run ();
454
+ } else {
455
+ ListenableFuture <Bitmap > bitmapFuture =
456
+ librarySessionImpl .getBitmapLoader ().decodeBitmap (metadata .artworkData );
457
+ bitmapFutures .add (bitmapFuture );
458
+ bitmapFuture .addListener (handleBitmapFuturesTask , MoreExecutors .directExecutor ());
459
+ }
460
+ }
461
+ return outputFuture ;
462
+ };
463
+ }
464
+
465
+ private void handleBitmapFuturesAllCompletedAndSetOutputFuture (
466
+ List <@ NullableType ListenableFuture <Bitmap >> bitmapFutures ,
467
+ List <MediaItem > mediaItems ,
468
+ SettableFuture <@ NullableType List <MediaBrowserCompat .MediaItem >> outputFuture ) {
469
+ List <MediaBrowserCompat .MediaItem > outputMediaItems = new ArrayList <>();
470
+ for (int i = 0 ; i < bitmapFutures .size (); i ++) {
471
+ @ Nullable ListenableFuture <Bitmap > future = bitmapFutures .get (i );
472
+ @ Nullable Bitmap bitmap = null ;
473
+ if (future != null ) {
474
+ try {
475
+ bitmap = Futures .getDone (future );
476
+ } catch (CancellationException | ExecutionException e ) {
477
+ Log .d (TAG , "Failed to get bitmap" );
478
+ }
479
+ }
480
+ outputMediaItems .add (MediaUtils .convertToBrowserItem (mediaItems .get (i ), bitmap ));
481
+ }
482
+ outputFuture .set (outputMediaItems );
483
+ }
484
+
485
+ private static <T > void cancelAllFutures (List <@ NullableType ListenableFuture <T >> futures ) {
486
+ for (int i = 0 ; i < futures .size (); i ++) {
487
+ if (futures .get (i ) != null ) {
488
+ futures .get (i ).cancel (/* mayInterruptIfRunning= */ false );
489
+ }
490
+ }
491
+ }
492
+
493
+ private AsyncFunction <LibraryResult <MediaItem >, MediaBrowserCompat .@ NullableType MediaItem >
494
+ createMediaItemToBrowserItemAsyncFunction () {
495
+ return result -> {
496
+ checkNotNull (result , "LibraryResult must not be null" );
497
+ SettableFuture <MediaBrowserCompat .@ NullableType MediaItem > outputFuture =
498
+ SettableFuture .create ();
499
+ if (result .resultCode != RESULT_SUCCESS || result .value == null ) {
500
+ outputFuture .set (null );
501
+ return outputFuture ;
502
+ }
503
+
504
+ MediaItem mediaItem = result .value ;
505
+ MediaMetadata metadata = mediaItem .mediaMetadata ;
506
+ if (metadata .artworkData == null ) {
507
+ outputFuture .set (MediaUtils .convertToBrowserItem (mediaItem , /* artworkBitmap= */ null ));
508
+ return outputFuture ;
509
+ }
510
+
511
+ ListenableFuture <Bitmap > bitmapFuture =
512
+ librarySessionImpl .getBitmapLoader ().decodeBitmap (metadata .artworkData );
513
+ outputFuture .addListener (
514
+ () -> {
515
+ if (outputFuture .isCancelled ()) {
516
+ bitmapFuture .cancel (/* mayInterruptIfRunning= */ false );
517
+ }
518
+ },
519
+ MoreExecutors .directExecutor ());
520
+ bitmapFuture .addListener (
521
+ () -> {
522
+ @ Nullable Bitmap bitmap = null ;
523
+ try {
524
+ bitmap = Futures .getDone (bitmapFuture );
525
+ } catch (CancellationException | ExecutionException e ) {
526
+ Log .d (TAG , "failed to get bitmap" );
527
+ }
528
+ outputFuture .set (MediaUtils .convertToBrowserItem (mediaItem , bitmap ));
529
+ },
530
+ MoreExecutors .directExecutor ());
531
+ return outputFuture ;
532
+ };
533
+ }
534
+
406
535
private static <T > void ignoreFuture (Future <T > unused ) {
407
536
// no-op
408
537
}
@@ -504,7 +633,9 @@ public void onSearchResultChanged(
504
633
ListenableFuture <LibraryResult <ImmutableList <MediaItem >>> future =
505
634
librarySessionImpl .onGetSearchResultOnHandler (
506
635
request .controller , request .query , page , pageSize , libraryParams );
507
- sendLibraryResultWithMediaItemsWhenReady (request .result , future );
636
+ ListenableFuture <@ NullableType List <MediaBrowserCompat .MediaItem >> mediaItemsFuture =
637
+ Util .transformFutureAsync (future , createMediaItemsToBrowserItemsAsyncFunction ());
638
+ sendLibraryResultWithMediaItemsWhenReady (request .result , mediaItemsFuture );
508
639
}
509
640
});
510
641
}
0 commit comments