Load preset input from JSON file in EffectActivity

The JSON file contains a list of playlists, each with a name and a list of media items. The EffectActivity loads the JSON file and creates a list of PlaylistHolder objects, which contain the playlist name and the list of media items.

PiperOrigin-RevId: 703069411
This commit is contained in:
shahddaghash 2024-12-05 04:43:40 -08:00 committed by Copybara-Service
parent f1a0e4b0b7
commit b6724e2115
4 changed files with 122 additions and 6 deletions

View File

@ -0,0 +1,27 @@
[
{
"name": "Cats -> Dogs",
"playlist": [
{
"uri": "https://html5demos.com/assets/dizzy.mp4"
},
{
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv"
}
]
},
{
"name": "Android Block -> Dogs -> BigBuckBunny",
"playlist": [
{
"uri": "https://storage.googleapis.com/exoplayer-test-media-0/android-block-1080-hevc.mp4"
},
{
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv"
},
{
"uri": "https://storage.googleapis.com/exoplayer-test-media-0/BigBuckBunny_320x180.mp4"
}
]
}
]

View File

@ -49,6 +49,7 @@ import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.common.util.Util.SDK_INT import androidx.media3.common.util.Util.SDK_INT
@ -60,11 +61,16 @@ class EffectActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { EffectDemo() } val playlistHolderList = mutableStateOf<List<PlaylistHolder>>(emptyList())
lifecycleScope.launch {
playlistHolderList.value =
loadPlaylistsFromJson(JSON_FILENAME, this@EffectActivity, "EffectActivity")
}
setContent { EffectDemo(playlistHolderList.value) }
} }
@Composable @Composable
fun EffectDemo() { private fun EffectDemo(playlistHolderList: List<PlaylistHolder>) {
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current val context = LocalContext.current
@ -100,7 +106,7 @@ class EffectActivity : ComponentActivity() {
} }
@Composable @Composable
fun InputChooser(onException: (String) -> Unit, onNewUri: (Uri) -> Unit) { private fun InputChooser(onException: (String) -> Unit, onNewUri: (Uri) -> Unit) {
var showLocalFilePicker by remember { mutableStateOf(false) } var showLocalFilePicker by remember { mutableStateOf(false) }
Row( Row(
modifier = Modifier.padding(vertical = dimensionResource(id = R.dimen.small_padding)), modifier = Modifier.padding(vertical = dimensionResource(id = R.dimen.small_padding)),
@ -128,7 +134,7 @@ class EffectActivity : ComponentActivity() {
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
@Composable @Composable
fun LocalFilePicker(onException: (String) -> Unit, onFileSelected: (Uri) -> Unit) { private fun LocalFilePicker(onException: (String) -> Unit, onFileSelected: (Uri) -> Unit) {
val context = LocalContext.current val context = LocalContext.current
val localFilePickerLauncher = val localFilePickerLauncher =
rememberLauncherForActivityResult( rememberLauncherForActivityResult(
@ -166,7 +172,7 @@ class EffectActivity : ComponentActivity() {
} }
@Composable @Composable
fun PlayerScreen(exoPlayer: ExoPlayer) { private fun PlayerScreen(exoPlayer: ExoPlayer) {
val context = LocalContext.current val context = LocalContext.current
AndroidView( AndroidView(
factory = { PlayerView(context).apply { player = exoPlayer } }, factory = { PlayerView(context).apply { player = exoPlayer } },
@ -177,9 +183,13 @@ class EffectActivity : ComponentActivity() {
} }
@Composable @Composable
fun Effects(onException: (String) -> Unit) { private fun Effects(onException: (String) -> Unit) {
Button(onClick = { onException("Button is not yet implemented.") }) { Button(onClick = { onException("Button is not yet implemented.") }) {
Text(text = stringResource(id = R.string.apply_effects)) Text(text = stringResource(id = R.string.apply_effects))
} }
} }
companion object {
const val JSON_FILENAME = "media.playlist.json"
}
} }

View File

@ -0,0 +1,78 @@
/*
* Copyright 2024 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 androidx.media3.demo.effect
import android.content.Context
import android.net.Uri
import android.util.JsonReader
import android.util.Log
import androidx.media3.common.MediaItem
import java.io.IOException
import java.io.InputStreamReader
import java.nio.charset.StandardCharsets
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
internal suspend fun loadPlaylistsFromJson(
jsonFilename: String,
context: Context,
tag: String,
): List<PlaylistHolder> =
withContext(Dispatchers.IO) {
try {
context.assets.open(jsonFilename).use { inputStream ->
val reader = JsonReader(InputStreamReader(inputStream, StandardCharsets.UTF_8))
val playlistHolders = buildList {
reader.beginArray()
while (reader.hasNext()) {
readPlaylist(reader)?.let { add(it) }
}
reader.endArray()
}
playlistHolders
}
} catch (e: IOException) {
Log.e(tag, context.getString(R.string.playlist_loading_error, jsonFilename, e))
emptyList()
}
}
private fun readPlaylist(reader: JsonReader): PlaylistHolder? {
val playlistHolder = PlaylistHolder("", emptyList())
reader.beginObject()
while (reader.hasNext()) {
val name = reader.nextName()
if (name.equals("name")) {
playlistHolder.title = reader.nextString()
} else if (name.equals("playlist")) {
playlistHolder.mediaItems = buildList {
reader.beginArray()
while (reader.hasNext()) {
reader.beginObject()
reader.nextName()
add(MediaItem.fromUri(Uri.parse(reader.nextString())))
reader.endObject()
}
reader.endArray()
}
}
}
reader.endObject()
// Only return the playlistHolder object if it has media items
return if (playlistHolder.mediaItems.isNotEmpty()) playlistHolder else null
}
internal data class PlaylistHolder(var title: String, var mediaItems: List<MediaItem>)

View File

@ -19,4 +19,5 @@
<string name="choose_local_file">Choose local file</string> <string name="choose_local_file">Choose local file</string>
<string name="apply_effects">Apply effects</string> <string name="apply_effects">Apply effects</string>
<string name="ok">OK</string> <string name="ok">OK</string>
<string name="playlist_loading_error">Error loading playlist from %1$s: %2$s</string>
</resources> </resources>