Merge pull request #615 from naveensingh/performance_improvements
Playback performance improvements
This commit is contained in:
commit
96c5bdfe6a
9 changed files with 171 additions and 157 deletions
|
@ -7,7 +7,7 @@ import androidx.media3.common.Player
|
|||
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||
import com.simplemobiletools.musicplayer.helpers.PlaybackSetting
|
||||
import com.simplemobiletools.musicplayer.models.Track
|
||||
import com.simplemobiletools.musicplayer.models.toMediaItems
|
||||
import com.simplemobiletools.musicplayer.models.toMediaItemsFast
|
||||
|
||||
val Player.isReallyPlaying: Boolean
|
||||
get() = when (playbackState) {
|
||||
|
@ -149,7 +149,7 @@ fun Player.prepareUsingTracks(
|
|||
return
|
||||
}
|
||||
|
||||
val mediaItems = tracks.toMediaItems()
|
||||
val mediaItems = tracks.toMediaItemsFast()
|
||||
runOnPlayerThread {
|
||||
setMediaItems(mediaItems, startIndex, startPositionMs)
|
||||
playWhenReady = play
|
||||
|
@ -164,8 +164,10 @@ fun Player.prepareUsingTracks(
|
|||
* items are added using [addRemainingMediaItems]. This helps prevent delays, especially with large queues, and
|
||||
* avoids potential issues like [android.app.ForegroundServiceStartNotAllowedException] when starting from background.
|
||||
*/
|
||||
var prepareInProgress = false
|
||||
inline fun Player.maybePreparePlayer(context: Context, crossinline callback: (success: Boolean) -> Unit) {
|
||||
if (currentMediaItem == null) {
|
||||
if (!prepareInProgress && currentMediaItem == null) {
|
||||
prepareInProgress = true
|
||||
ensureBackgroundThread {
|
||||
var prepared = false
|
||||
context.audioHelper.getQueuedTracksLazily { tracks, startIndex, startPositionMs ->
|
||||
|
@ -179,7 +181,7 @@ inline fun Player.maybePreparePlayer(context: Context, crossinline callback: (su
|
|||
return@getQueuedTracksLazily
|
||||
}
|
||||
|
||||
addRemainingMediaItems(tracks.toMediaItems(), startIndex)
|
||||
addRemainingMediaItems(tracks.toMediaItemsFast(), startIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.simplemobiletools.musicplayer.models
|
|||
import android.content.ContentUris
|
||||
import android.net.Uri
|
||||
import android.provider.MediaStore
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Index
|
||||
|
@ -101,3 +102,9 @@ data class Track(
|
|||
fun ArrayList<Track>.sortSafely(sorting: Int) = sortSafely(Track.getComparator(sorting))
|
||||
|
||||
fun Collection<Track>.toMediaItems() = map { it.toMediaItem() }
|
||||
|
||||
fun Collection<Track>.toMediaItemsFast() = map {
|
||||
MediaItem.Builder()
|
||||
.setMediaId(it.mediaStoreId.toString())
|
||||
.build()
|
||||
}
|
||||
|
|
|
@ -166,10 +166,11 @@ internal fun PlaybackService.getMediaSessionCallback() = object : MediaLibrarySe
|
|||
mediaItems: MutableList<MediaItem>,
|
||||
startIndex: Int,
|
||||
startPositionMs: Long
|
||||
) = if (controller.packageName == packageName) {
|
||||
Futures.immediateFuture(MediaSession.MediaItemsWithStartPosition(mediaItems, startIndex, startPositionMs))
|
||||
} else {
|
||||
callWhenSourceReady {
|
||||
): ListenableFuture<MediaSession.MediaItemsWithStartPosition> {
|
||||
if (controller.packageName == packageName) {
|
||||
return super.onSetMediaItems(mediaSession, controller, mediaItems, startIndex, startPositionMs)
|
||||
}
|
||||
|
||||
// this is to avoid single items in the queue: https://github.com/androidx/media/issues/156
|
||||
var queueItems = mediaItems
|
||||
val startItemId = mediaItems[0].mediaId
|
||||
|
@ -182,36 +183,27 @@ internal fun PlaybackService.getMediaSessionCallback() = object : MediaLibrarySe
|
|||
}
|
||||
|
||||
val startItemIndex = queueItems.indexOfFirst { it.mediaId == startItemId }
|
||||
super.onSetMediaItems(mediaSession, controller, queueItems, startItemIndex, startPositionMs).get()
|
||||
}
|
||||
return super.onSetMediaItems(mediaSession, controller, queueItems, startItemIndex, startPositionMs)
|
||||
}
|
||||
|
||||
override fun onAddMediaItems(
|
||||
mediaSession: MediaSession,
|
||||
controller: MediaSession.ControllerInfo,
|
||||
mediaItems: List<MediaItem>
|
||||
) = if (controller.packageName == packageName) {
|
||||
Futures.immediateFuture(mediaItems)
|
||||
} else {
|
||||
callWhenSourceReady {
|
||||
mediaItems.map { mediaItem ->
|
||||
): ListenableFuture<List<MediaItem>> {
|
||||
val items = mediaItems.map { mediaItem ->
|
||||
if (mediaItem.requestMetadata.searchQuery != null) {
|
||||
getMediaItemFromSearchQuery(mediaItem.requestMetadata.searchQuery!!)
|
||||
} else {
|
||||
mediaItemProvider[mediaItem.mediaId] ?: mediaItem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Futures.immediateFuture(items)
|
||||
}
|
||||
|
||||
private fun getMediaItemFromSearchQuery(query: String): MediaItem {
|
||||
val searchQuery = if (query.startsWith("play ", ignoreCase = true)) {
|
||||
query.drop(5).lowercase()
|
||||
} else {
|
||||
query.lowercase()
|
||||
}
|
||||
|
||||
return mediaItemProvider.getItemFromSearch(searchQuery) ?: mediaItemProvider.getRandomItem()
|
||||
return mediaItemProvider.getItemFromSearch(query.lowercase()) ?: mediaItemProvider.getRandomItem()
|
||||
}
|
||||
|
||||
private fun reloadContent() {
|
||||
|
|
|
@ -12,7 +12,7 @@ import androidx.media3.common.MediaMetadata
|
|||
import androidx.media3.common.MediaMetadata.MediaType
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.session.MediaSession.MediaItemsWithStartPosition
|
||||
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||
import com.google.common.util.concurrent.MoreExecutors
|
||||
import com.simplemobiletools.musicplayer.R
|
||||
import com.simplemobiletools.musicplayer.extensions.*
|
||||
import com.simplemobiletools.musicplayer.helpers.TAB_ALBUMS
|
||||
|
@ -23,6 +23,7 @@ import com.simplemobiletools.musicplayer.helpers.TAB_PLAYLISTS
|
|||
import com.simplemobiletools.musicplayer.helpers.TAB_TRACKS
|
||||
import com.simplemobiletools.musicplayer.models.QueueItem
|
||||
import com.simplemobiletools.musicplayer.models.toMediaItems
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
private const val STATE_CREATED = 1
|
||||
private const val STATE_INITIALIZING = 2
|
||||
|
@ -42,6 +43,9 @@ private const val SMP_GENRES_ROOT_ID = "__GENRES__"
|
|||
*/
|
||||
@UnstableApi
|
||||
internal class MediaItemProvider(private val context: Context) {
|
||||
private val executor by lazy {
|
||||
MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor())
|
||||
}
|
||||
|
||||
inner class MediaItemNode(val item: MediaItem) {
|
||||
private val children: MutableList<MediaItem> = ArrayList()
|
||||
|
@ -89,7 +93,16 @@ internal class MediaItemProvider(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
operator fun get(mediaId: String) = getNode(mediaId)?.item
|
||||
operator fun get(mediaId: String): MediaItem? {
|
||||
val mediaItem = getNode(mediaId)?.item
|
||||
if (mediaItem == null) {
|
||||
// assume it's a track
|
||||
val mediaStoreId = mediaId.toLongOrNull() ?: return null
|
||||
return audioHelper.getTrack(mediaStoreId)?.toMediaItem()
|
||||
}
|
||||
|
||||
return mediaItem
|
||||
}
|
||||
|
||||
fun getRootItem() = get(SMP_ROOT_ID)!!
|
||||
|
||||
|
@ -145,7 +158,7 @@ internal class MediaItemProvider(private val context: Context) {
|
|||
return
|
||||
}
|
||||
|
||||
ensureBackgroundThread {
|
||||
executor.execute {
|
||||
val trackId = current.mediaId.toLong()
|
||||
val queueItems = mediaItems.mapIndexed { index, mediaItem ->
|
||||
QueueItem(trackId = mediaItem.mediaId.toLong(), trackOrder = index, isCurrent = false, lastPosition = 0)
|
||||
|
@ -157,8 +170,7 @@ internal class MediaItemProvider(private val context: Context) {
|
|||
|
||||
fun reload() {
|
||||
state = STATE_INITIALIZING
|
||||
|
||||
ensureBackgroundThread {
|
||||
executor.execute {
|
||||
buildRoot()
|
||||
|
||||
try {
|
||||
|
|
|
@ -22,7 +22,7 @@ private const val SKIP_SILENCE_THRESHOLD_LEVEL = 16.toShort()
|
|||
@UnstableApi
|
||||
class AudioOnlyRenderersFactory(context: Context) : DefaultRenderersFactory(context) {
|
||||
|
||||
override fun buildAudioSink(context: Context, enableFloatOutput: Boolean, enableAudioTrackPlaybackParams: Boolean, enableOffload: Boolean): AudioSink? {
|
||||
override fun buildAudioSink(context: Context, enableFloatOutput: Boolean, enableAudioTrackPlaybackParams: Boolean): AudioSink {
|
||||
val silenceSkippingAudioProcessor = SilenceSkippingAudioProcessor(
|
||||
SKIP_SILENCE_MINIMUM_DURATION_US,
|
||||
DEFAULT_PADDING_SILENCE_US,
|
||||
|
@ -32,13 +32,6 @@ class AudioOnlyRenderersFactory(context: Context) : DefaultRenderersFactory(cont
|
|||
return DefaultAudioSink.Builder(context)
|
||||
.setEnableFloatOutput(enableFloatOutput)
|
||||
.setEnableAudioTrackPlaybackParams(enableAudioTrackPlaybackParams)
|
||||
.setOffloadMode(
|
||||
if (enableOffload) {
|
||||
DefaultAudioSink.OFFLOAD_MODE_ENABLED_GAPLESS_REQUIRED
|
||||
} else {
|
||||
DefaultAudioSink.OFFLOAD_MODE_DISABLED
|
||||
}
|
||||
)
|
||||
.setAudioProcessorChain(
|
||||
DefaultAudioSink.DefaultAudioProcessorChain(
|
||||
arrayOf(),
|
||||
|
|
|
@ -8,12 +8,19 @@ import androidx.media3.exoplayer.ExoPlayer
|
|||
import androidx.media3.exoplayer.source.ShuffleOrder.DefaultShuffleOrder
|
||||
import com.simplemobiletools.musicplayer.extensions.*
|
||||
import com.simplemobiletools.musicplayer.inlines.indexOfFirstOrNull
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
private const val DEFAULT_SHUFFLE_ORDER_SEED = 42L
|
||||
|
||||
@UnstableApi
|
||||
class SimpleMusicPlayer(private val exoPlayer: ExoPlayer) : ForwardingPlayer(exoPlayer) {
|
||||
|
||||
private var seekToNextCount = 0
|
||||
private var seekToPreviousCount = 0
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.Default)
|
||||
private var seekJob: Job? = null
|
||||
|
||||
/**
|
||||
* The default implementation only advertises the seek to next and previous item in the case
|
||||
* that it's not the first or last track. We manually advertise that these
|
||||
|
@ -53,28 +60,32 @@ class SimpleMusicPlayer(private val exoPlayer: ExoPlayer) : ForwardingPlayer(exo
|
|||
override fun seekToNext() {
|
||||
play()
|
||||
if (!maybeForceNext()) {
|
||||
super.seekToNext()
|
||||
seekToNextCount += 1
|
||||
seekWithDelay()
|
||||
}
|
||||
}
|
||||
|
||||
override fun seekToPrevious() {
|
||||
play()
|
||||
if (!maybeForcePrevious()) {
|
||||
super.seekToPrevious()
|
||||
seekToPreviousCount += 1
|
||||
seekWithDelay()
|
||||
}
|
||||
}
|
||||
|
||||
override fun seekToNextMediaItem() {
|
||||
play()
|
||||
if (!maybeForceNext()) {
|
||||
super.seekToNextMediaItem()
|
||||
seekToNextCount += 1
|
||||
seekWithDelay()
|
||||
}
|
||||
}
|
||||
|
||||
override fun seekToPreviousMediaItem() {
|
||||
play()
|
||||
if (!maybeForcePrevious()) {
|
||||
super.seekToPreviousMediaItem()
|
||||
seekToPreviousCount += 1
|
||||
seekWithDelay()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,4 +133,36 @@ class SimpleMusicPlayer(private val exoPlayer: ExoPlayer) : ForwardingPlayer(exo
|
|||
exoPlayer.setShuffleOrder(DefaultShuffleOrder(shuffledIndices.toIntArray(), DEFAULT_SHUFFLE_ORDER_SEED))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is here so the player can quickly seek next/previous without doing too much work.
|
||||
* It probably won't be needed once https://github.com/androidx/media/issues/81 is resolved.
|
||||
*/
|
||||
private fun seekWithDelay() {
|
||||
seekJob?.cancel()
|
||||
seekJob = scope.launch {
|
||||
delay(timeMillis = 400)
|
||||
if (seekToNextCount > 0 || seekToPreviousCount > 0) {
|
||||
runOnPlayerThread {
|
||||
if (currentMediaItem != null) {
|
||||
if (seekToNextCount > 0) {
|
||||
seekTo(rotateIndex(currentMediaItemIndex + seekToNextCount), 0)
|
||||
}
|
||||
|
||||
if (seekToPreviousCount > 0) {
|
||||
seekTo(rotateIndex(currentMediaItemIndex - seekToPreviousCount), 0)
|
||||
}
|
||||
|
||||
seekToNextCount = 0
|
||||
seekToPreviousCount = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun rotateIndex(index: Int): Int {
|
||||
val count = mediaItemCount
|
||||
return (index % count + count) % count
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,108 +1,79 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/track_frame"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/tiny_margin"
|
||||
android:paddingStart="@dimen/medium_margin"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="@dimen/normal_margin"
|
||||
android:paddingTop="@dimen/activity_margin"
|
||||
android:paddingEnd="@dimen/medium_margin"
|
||||
android:paddingBottom="@dimen/activity_margin">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="@dimen/small_margin"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/track_image"
|
||||
android:layout_width="@dimen/song_image_size"
|
||||
android:layout_height="@dimen/song_image_size"
|
||||
android:layout_centerVertical="true"
|
||||
android:visibility="gone" />
|
||||
|
||||
<com.simplemobiletools.commons.views.MyTextView
|
||||
android:id="@+id/track_id"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toEndOf="@id/track_image"
|
||||
android:layout_gravity="center"
|
||||
android:ems="2"
|
||||
android:gravity="end"
|
||||
android:paddingEnd="@dimen/small_margin"
|
||||
android:textSize="@dimen/bigger_text_size"
|
||||
tools:text="1" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toStartOf="@id/layout_duration_handle"
|
||||
android:layout_toEndOf="@id/track_id"
|
||||
android:paddingStart="@dimen/normal_margin"
|
||||
android:paddingEnd="@dimen/normal_margin">
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="@dimen/normal_margin">
|
||||
|
||||
<com.simplemobiletools.commons.views.MyTextView
|
||||
android:id="@+id/track_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textSize="@dimen/bigger_text_size"
|
||||
app:layout_constraintBottom_toTopOf="@id/track_info"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Track title" />
|
||||
|
||||
<com.simplemobiletools.commons.views.MyTextView
|
||||
android:id="@+id/track_info"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:alpha="0.6"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textSize="@dimen/normal_text_size"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/track_title"
|
||||
tools:text="Track artist" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/layout_duration_handle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true">
|
||||
</LinearLayout>
|
||||
|
||||
<com.simplemobiletools.commons.views.MyTextView
|
||||
android:id="@+id/track_duration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:paddingStart="@dimen/normal_margin"
|
||||
android:paddingEnd="@dimen/medium_margin"
|
||||
android:textSize="@dimen/bigger_text_size"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/track_drag_handle"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="3:45" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/track_drag_handle"
|
||||
android:layout_width="@dimen/song_image_size"
|
||||
android:layout_height="@dimen/song_image_size"
|
||||
android:layout_gravity="center|end"
|
||||
android:padding="@dimen/medium_margin"
|
||||
android:src="@drawable/ic_drag_handle_vector"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
android:visibility="gone" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</RelativeLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</LinearLayout>
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/track_queue_frame"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/tiny_margin"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="@dimen/normal_margin"
|
||||
android:paddingTop="@dimen/activity_margin"
|
||||
android:paddingEnd="@dimen/medium_margin"
|
||||
|
@ -23,7 +24,8 @@
|
|||
android:id="@+id/track_queue_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="1"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:paddingStart="@dimen/normal_margin"
|
||||
|
@ -38,27 +40,19 @@
|
|||
|
||||
<com.simplemobiletools.commons.views.MyTextView
|
||||
android:id="@+id/track_queue_duration"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginEnd="@dimen/medium_margin"
|
||||
android:gravity="end"
|
||||
android:textSize="@dimen/bigger_text_size"
|
||||
app:flow_verticalAlign="center"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/track_queue_drag_handle"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="3:45" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/track_queue_drag_handle"
|
||||
android:layout_width="@dimen/song_image_size"
|
||||
android:layout_height="@dimen/song_image_size"
|
||||
android:layout_gravity="center"
|
||||
android:padding="@dimen/medium_margin"
|
||||
android:src="@drawable/ic_drag_handle_vector"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
android:src="@drawable/ic_drag_handle_vector" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</LinearLayout>
|
||||
|
|
|
@ -18,7 +18,7 @@ eventbus = "3.3.1"
|
|||
lottie = "6.1.0"
|
||||
m3uParser = "1.3.0"
|
||||
media = "1.6.0"
|
||||
media3 = "1.1.1"
|
||||
media3 = "1.2.0-alpha02"
|
||||
room = "2.5.2"
|
||||
#Simple Mobile Tools
|
||||
simple-commons = "e1603ee2d6"
|
||||
|
|
Loading…
Reference in a new issue