From b6724e211585b2cb2fce0caf5929b760f077acc4 Mon Sep 17 00:00:00 2001 From: shahddaghash Date: Thu, 5 Dec 2024 04:43:40 -0800 Subject: [PATCH] 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 --- .../src/main/assets/media.playlist.json | 27 +++++++ .../media3/demo/effect/EffectActivity.kt | 22 ++++-- .../media3/demo/effect/EffectDemoUtil.kt | 78 +++++++++++++++++++ demos/effect/src/main/res/values/strings.xml | 1 + 4 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 demos/effect/src/main/assets/media.playlist.json create mode 100644 demos/effect/src/main/java/androidx/media3/demo/effect/EffectDemoUtil.kt diff --git a/demos/effect/src/main/assets/media.playlist.json b/demos/effect/src/main/assets/media.playlist.json new file mode 100644 index 0000000000..63586baef8 --- /dev/null +++ b/demos/effect/src/main/assets/media.playlist.json @@ -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" + } + ] + } +] diff --git a/demos/effect/src/main/java/androidx/media3/demo/effect/EffectActivity.kt b/demos/effect/src/main/java/androidx/media3/demo/effect/EffectActivity.kt index 4d62905378..bd50296a59 100644 --- a/demos/effect/src/main/java/androidx/media3/demo/effect/EffectActivity.kt +++ b/demos/effect/src/main/java/androidx/media3/demo/effect/EffectActivity.kt @@ -49,6 +49,7 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat +import androidx.lifecycle.lifecycleScope import androidx.media3.common.MediaItem import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.Util.SDK_INT @@ -60,11 +61,16 @@ class EffectActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContent { EffectDemo() } + val playlistHolderList = mutableStateOf>(emptyList()) + lifecycleScope.launch { + playlistHolderList.value = + loadPlaylistsFromJson(JSON_FILENAME, this@EffectActivity, "EffectActivity") + } + setContent { EffectDemo(playlistHolderList.value) } } @Composable - fun EffectDemo() { + private fun EffectDemo(playlistHolderList: List) { val snackbarHostState = remember { SnackbarHostState() } val coroutineScope = rememberCoroutineScope() val context = LocalContext.current @@ -100,7 +106,7 @@ class EffectActivity : ComponentActivity() { } @Composable - fun InputChooser(onException: (String) -> Unit, onNewUri: (Uri) -> Unit) { + private fun InputChooser(onException: (String) -> Unit, onNewUri: (Uri) -> Unit) { var showLocalFilePicker by remember { mutableStateOf(false) } Row( modifier = Modifier.padding(vertical = dimensionResource(id = R.dimen.small_padding)), @@ -128,7 +134,7 @@ class EffectActivity : ComponentActivity() { @OptIn(UnstableApi::class) @Composable - fun LocalFilePicker(onException: (String) -> Unit, onFileSelected: (Uri) -> Unit) { + private fun LocalFilePicker(onException: (String) -> Unit, onFileSelected: (Uri) -> Unit) { val context = LocalContext.current val localFilePickerLauncher = rememberLauncherForActivityResult( @@ -166,7 +172,7 @@ class EffectActivity : ComponentActivity() { } @Composable - fun PlayerScreen(exoPlayer: ExoPlayer) { + private fun PlayerScreen(exoPlayer: ExoPlayer) { val context = LocalContext.current AndroidView( factory = { PlayerView(context).apply { player = exoPlayer } }, @@ -177,9 +183,13 @@ class EffectActivity : ComponentActivity() { } @Composable - fun Effects(onException: (String) -> Unit) { + private fun Effects(onException: (String) -> Unit) { Button(onClick = { onException("Button is not yet implemented.") }) { Text(text = stringResource(id = R.string.apply_effects)) } } + + companion object { + const val JSON_FILENAME = "media.playlist.json" + } } diff --git a/demos/effect/src/main/java/androidx/media3/demo/effect/EffectDemoUtil.kt b/demos/effect/src/main/java/androidx/media3/demo/effect/EffectDemoUtil.kt new file mode 100644 index 0000000000..c02f1d0830 --- /dev/null +++ b/demos/effect/src/main/java/androidx/media3/demo/effect/EffectDemoUtil.kt @@ -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 = + 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) diff --git a/demos/effect/src/main/res/values/strings.xml b/demos/effect/src/main/res/values/strings.xml index 25c8596b99..8a48c99821 100644 --- a/demos/effect/src/main/res/values/strings.xml +++ b/demos/effect/src/main/res/values/strings.xml @@ -19,4 +19,5 @@ Choose local file Apply effects OK + Error loading playlist from %1$s: %2$s