diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 351974d0..ad5037a8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -57,7 +57,7 @@ maps-compose = "6.6.0" material = "1.14.0-alpha02" material3-adaptive = "1.1.0" material3-adaptive-navigation-suite = "1.3.2" -media3 = "1.7.1" +media3 = "1.8.0" # @keep minSdk = "35" okHttp = "4.12.0" @@ -76,6 +76,8 @@ wearComposeMaterial3 = "1.5.0-beta04" wearOngoing = "1.0.0" wearToolingPreview = "1.0.0" webkit = "1.14.0" +media3CommonKtx = "1.8.0" +media3Exoplayer = "1.8.0" [libraries] accompanist-adaptive = "com.google.accompanist:accompanist-adaptive:0.37.3" @@ -187,6 +189,8 @@ play-services-wearable = { module = "com.google.android.gms:play-services-wearab validator-push = { module = "com.google.android.wearable.watchface.validator:validator-push", version.ref = "validatorPush" } wear-compose-material = { module = "androidx.wear.compose:compose-material", version.ref = "wearComposeMaterial" } wear-compose-material3 = { module = "androidx.wear.compose:compose-material3", version.ref = "wearComposeMaterial3" } +androidx-media3-common-ktx = { group = "androidx.media3", name = "media3-common-ktx", version.ref = "media3CommonKtx" } +media3-exoplayer = { group = "androidx.media3", name = "media3-exoplayer", version.ref = "media3Exoplayer" } [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } diff --git a/misc/build.gradle.kts b/misc/build.gradle.kts index af5893d7..4c4e8193 100644 --- a/misc/build.gradle.kts +++ b/misc/build.gradle.kts @@ -46,6 +46,8 @@ android { } dependencies { + implementation(libs.androidx.media3.common.ktx) + implementation(libs.media3.exoplayer) val composeBom = platform(libs.androidx.compose.bom) implementation(composeBom) androidTestImplementation(composeBom) diff --git a/misc/src/main/java/com/example/snippets/PreloadManagerKotlinSnippets.kt b/misc/src/main/java/com/example/snippets/PreloadManagerKotlinSnippets.kt new file mode 100644 index 00000000..f8ffe5cc --- /dev/null +++ b/misc/src/main/java/com/example/snippets/PreloadManagerKotlinSnippets.kt @@ -0,0 +1,134 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.snippets + +import android.os.Bundle +import androidx.annotation.OptIn +import androidx.appcompat.app.AppCompatActivity +import androidx.media3.common.C +import androidx.media3.common.MediaItem +import androidx.media3.common.util.UnstableApi +import androidx.media3.exoplayer.ExoPlayer +import androidx.media3.exoplayer.source.preload.DefaultPreloadManager +import androidx.media3.exoplayer.source.preload.TargetPreloadStatusControl +import java.lang.Math.abs + +// constants to make the code snippets work +const val currentPlayingIndex = 10 + +@UnstableApi +// [START android_defaultpreloadmanager_MyTargetPreloadStatusControl] +class MyTargetPreloadStatusControl( + currentPlayingIndex: Int = C.INDEX_UNSET +) : TargetPreloadStatusControl { + + override fun getTargetPreloadStatus(index: Int): DefaultPreloadManager.PreloadStatus? { + if (index - currentPlayingIndex == 1) { // next track + // return a PreloadStatus that is labelled by STAGE_SPECIFIED_RANGE_LOADED and + // suggest loading 3000ms from the default start position + return DefaultPreloadManager.PreloadStatus.specifiedRangeLoaded(3000L) + } else if (index - currentPlayingIndex == -1) { // previous track + // return a PreloadStatus that is labelled by STAGE_SPECIFIED_RANGE_LOADED and + // suggest loading 3000ms from the default start position + return DefaultPreloadManager.PreloadStatus.specifiedRangeLoaded(3000L) + } else if (abs(index - currentPlayingIndex) == 2) { + // return a PreloadStatus that is labelled by STAGE_TRACKS_SELECTED + return DefaultPreloadManager.PreloadStatus.TRACKS_SELECTED + } else if (abs(index - currentPlayingIndex) <= 4) { + // return a PreloadStatus that is labelled by STAGE_SOURCE_PREPARED + return DefaultPreloadManager.PreloadStatus.SOURCE_PREPARED + } + return null + } +} +// [END android_defaultpreloadmanager_MyTargetPreloadStatusControl] + +class PreloadManagerSnippetsKotlin { + + class PreloadSnippetsActivity : AppCompatActivity() { + private val context = this + + @OptIn(UnstableApi::class) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // [START android_defaultpreloadmanager_createPLM] + val targetPreloadStatusControl = MyTargetPreloadStatusControl() + val preloadManagerBuilder = + DefaultPreloadManager.Builder(context, targetPreloadStatusControl) + val preloadManager = preloadManagerBuilder.build() + // [END android_defaultpreloadmanager_createPLM] + + val player = preloadManagerBuilder.buildExoPlayer() + + // [START android_defaultpreloadmanager_addMedia] + val initialMediaItems = pullMediaItemsFromService(/* count= */ 20) + for (index in 0 until initialMediaItems.size) { + preloadManager.add(initialMediaItems.get(index), /* rankingData= */ index) + } + // items aren't actually loaded yet! need to call invalidate() after this + // [END android_defaultpreloadmanager_addMedia] + + // [START android_defaultpreloadmanager_invalidate] + preloadManager.invalidate() + // [END android_defaultpreloadmanager_invalidate] + } + + @OptIn(UnstableApi::class) + private fun fetchMedia( + preloadManager: DefaultPreloadManager, + mediaItem: MediaItem, + player: ExoPlayer, + currentIndex: Int + ) { + // [START android_defaultpreloadmanager_getAndPlayMedia] + // When a media item is about to display on the screen + val mediaSource = preloadManager.getMediaSource(mediaItem) + if (mediaSource != null) { + player.setMediaSource(mediaSource) + } + player.prepare() + + // When the media item is displaying at the center of the screen + player.play() + preloadManager.setCurrentPlayingIndex(currentIndex) + + // Need to call invalidate() to update the priorities + preloadManager.invalidate() + // [END android_defaultpreloadmanager_getAndPlayMedia] + } + + @OptIn(UnstableApi::class) + private fun removeMedia(mediaItem: MediaItem, preloadManager: DefaultPreloadManager) { + // [START android_defaultpreloadmanager_removeItem] + preloadManager.remove(mediaItem) + // [END android_defaultpreloadmanager_removeItem] + } + + @OptIn(UnstableApi::class) + private fun releasePLM(preloadManager: DefaultPreloadManager) { + // [START android_defaultpreloadmanager_releasePLM] + preloadManager.release() + // [END android_defaultpreloadmanager_releasePLM] + } + + // dummy methods to support the code snippets + private fun pullMediaItemsFromService(count: Int): List { + return listOf() + } + } +}