Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions demos/shortform/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
apply from: '../../constants.gradle'
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'org.jetbrains.kotlin.plugin.compose'

android {
namespace 'androidx.media3.demo.shortform'
Expand Down Expand Up @@ -57,6 +58,7 @@ android {
}
buildFeatures {
viewBinding true
compose true
}
sourceSets {
main {
Expand All @@ -80,4 +82,14 @@ dependencies {
implementation project(modulePrefix + 'lib-exoplayer-dash')
implementation project(modulePrefix + 'lib-exoplayer-hls')
implementation project(modulePrefix + 'lib-ui')

// Compose dependencies
def composeBom = platform('androidx.compose:compose-bom:2024.12.01')
implementation composeBom
implementation 'androidx.activity:activity-compose:1.9.0'
implementation 'androidx.compose.foundation:foundation'
implementation 'androidx.compose.material3:material3'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.compose.runtime:runtime'
debugImplementation 'androidx.compose.ui:ui-tooling'
}
4 changes: 4 additions & 0 deletions demos/shortform/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
android:exported="false"
android:label="@string/title_activity_view_pager"
android:name=".viewpager.ViewPagerActivity"/>
<activity
android:exported="false"
android:label="@string/title_activity_lazy_column"
android:name=".lazycolumn.LazyColumnActivity"/>
</application>

<uses-permission android:name="android.permission.INTERNET" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import android.view.View
import android.widget.EditText
import androidx.appcompat.app.AppCompatActivity
import androidx.media3.common.util.UnstableApi
import androidx.media3.demo.shortform.lazycolumn.LazyColumnActivity
import androidx.media3.demo.shortform.viewpager.ViewPagerActivity
import java.lang.Integer.max
import java.lang.Integer.min
Expand Down Expand Up @@ -56,6 +57,12 @@ class MainActivity : AppCompatActivity() {
Intent(this, ViewPagerActivity::class.java).putExtra(NUM_PLAYERS_EXTRA, numberOfPlayers)
)
}

findViewById<View>(R.id.lazy_column_button).setOnClickListener {
startActivity(
Intent(this, LazyColumnActivity::class.java).putExtra(NUM_PLAYERS_EXTRA, numberOfPlayers)
)
}
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,6 @@ class MediaItemDatabase {
val uri = mediaUris[index.mod(mediaUris.size)]
return MediaItem.Builder().setUri(uri).setMediaId(index.toString()).build()
}

fun size(): Int = mediaUris.size
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package androidx.media3.demo.shortform.lazycolumn

import android.graphics.Bitmap
import android.media.MediaMetadataRetriever
import android.os.Build

class BitmapProvider {
companion object {
private const val THUMB_EXTRACT_TIME = 1 * 1000L * 1000L
}

fun getBitmap(
ids: List<String?>?,
metadataRetriever: MediaMetadataRetriever,
): Bitmap? {
if (ids.isNullOrEmpty()) return null
if (ids.size > 1) {
val customDataSource = ConcatMediaDataSource(ids)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
metadataRetriever.setDataSource(customDataSource)
}
} else {
metadataRetriever.setDataSource(ids.getOrNull(0))
}
return metadataRetriever.getFrameAtTime(0) ?: metadataRetriever.getFrameAtTime(
THUMB_EXTRACT_TIME
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package androidx.media3.demo.shortform.lazycolumn

import android.annotation.SuppressLint
import android.media.MediaDataSource
import java.io.ByteArrayOutputStream
import java.io.FileInputStream
import java.io.SequenceInputStream
import java.util.Enumeration
import kotlin.math.min

@SuppressLint("NewApi")
class ConcatMediaDataSource(ids: List<String?>) : MediaDataSource() {

private var data: ByteArray? = null
private val mergedInputStream: SequenceInputStream by lazy { getInputStream(ids) }

private fun ensureDataBuffered() {
synchronized(this) {
if (data != null) return
ByteArrayOutputStream().use { buffer ->
mergedInputStream.use { input ->
input.copyTo(buffer)
}
data = buffer.toByteArray()
}
}
}

override fun readAt(
position: Long,
buffer: ByteArray,
offset: Int,
size: Int,
): Int {
synchronized(this) {
ensureDataBuffered()
data?.let {
if (position > it.size) return -1
val actualSize = min(size, it.size - position.toInt())
System.arraycopy(it, position.toInt(), buffer, offset, actualSize)
return actualSize
} ?: run {
return -1
}
}
}

override fun getSize(): Long {
synchronized(this) {
return (data?.size?.toLong() ?: -1)
}
}

override fun close() {
synchronized(this) {
mergedInputStream.close()
data = null
}
}

private fun getInputStream(files: List<String?>): SequenceInputStream {
val inputStreams = files.map { FileInputStream(it) }
val allInputStreams = listOf(*inputStreams.toTypedArray())
return SequenceInputStream(allInputStreams.iterator().asEnumeration())
}
}

fun <T> Iterator<T>.asEnumeration(): Enumeration<T> {
return object : Enumeration<T> {
override fun hasMoreElements(): Boolean = this@asEnumeration.hasNext()

override fun nextElement(): T = this@asEnumeration.next()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright 2023 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
*
* http://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.shortform.lazycolumn

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.media3.common.util.UnstableApi
import androidx.media3.demo.shortform.lazycolumn.composable.ShortFormLazyColumn

@UnstableApi
class LazyColumnActivity : ComponentActivity() {
companion object {
const val LOAD_CONTROL_MIN_BUFFER_MS = 3_000
const val LOAD_CONTROL_MAX_BUFFER_MS = 20_000
const val LOAD_CONTROL_BUFFER_FOR_PLAYBACK_MS = 500
const val PLAYER_CACHE_DIRECTORY = "exo_player"
}


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Surface(
modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background
) {
ShortFormContent()
}
}
}
}
}

@UnstableApi
@Composable
fun ShortFormContent() {
val context = LocalContext.current
val playerManager = remember {
LazyColumnPlayerManager(context)
}

DisposableEffect(Unit) {
onDispose {
playerManager.release()
}
}

ShortFormLazyColumn(
playerManager = playerManager
)
}
Loading