music: try to fix extractor thread starvation

This commit is contained in:
Alexander Capehart 2024-11-27 15:16:30 -07:00
parent ffaff6f08e
commit d52e301751
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47

View file

@ -61,7 +61,10 @@ constructor(
override fun extract(deviceFiles: Flow<DeviceFile>) = flow { override fun extract(deviceFiles: Flow<DeviceFile>) = flow {
val retriever = ChunkedMetadataRetriever(mediaSourceFactory) val retriever = ChunkedMetadataRetriever(mediaSourceFactory)
deviceFiles.collect { deviceFile -> deviceFiles.collect { deviceFile ->
val exoPlayerMetadataFuture = retriever.push(deviceFile.uri) // val exoPlayerMetadataFuture =
// MetadataRetriever.retrieveMetadata(
// mediaSourceFactory, MediaItem.fromUri(deviceFile.uri))
val exoPlayerMetadataFuture = retriever.retrieve(deviceFile.uri)
val mediaMetadataRetriever = MediaMetadataRetriever() val mediaMetadataRetriever = MediaMetadataRetriever()
mediaMetadataRetriever.setDataSource(context, deviceFile.uri) mediaMetadataRetriever.setDataSource(context, deviceFile.uri)
val exoPlayerMetadata = exoPlayerMetadataFuture.asDeferred().await() val exoPlayerMetadata = exoPlayerMetadataFuture.asDeferred().await()
@ -69,7 +72,7 @@ constructor(
mediaMetadataRetriever.close() mediaMetadataRetriever.close()
emit(result) emit(result)
} }
retriever.stop() retriever.release()
} }
private fun extractTags( private fun extractTags(
@ -132,10 +135,10 @@ constructor(
requireNotNull(a) { "Invalid tag, missing $called" } requireNotNull(a) { "Invalid tag, missing $called" }
} }
private const val MESSAGE_CHECK_JOBS = 0 private const val MESSAGE_PREPARE = 0
private const val MESSAGE_CONTINUE_LOADING = 1 private const val MESSAGE_CONTINUE_LOADING = 1
private const val MESSAGE_RELEASE = 2 private const val MESSAGE_CHECK_FAILURE = 2
private const val MESSAGE_RELEASE_ALL = 3 private const val MESSAGE_RELEASE = 3
private const val CHECK_INTERVAL_MS = 100 private const val CHECK_INTERVAL_MS = 100
/** /**
@ -150,95 +153,85 @@ private class ChunkedMetadataRetriever(private val mediaSourceFactory: MediaSour
private val mediaSourceHandler: HandlerWrapper private val mediaSourceHandler: HandlerWrapper
private var job: MetadataJob? = null private var job: MetadataJob? = null
private class MetadataJob( private data class JobParams(val uri: Uri, val future: SettableFuture<TrackGroupArray>)
val mediaItem: MediaItem,
val future: SettableFuture<TrackGroupArray>, private class JobData(
var mediaSource: MediaSource?, val params: JobParams,
val mediaSource: MediaSource,
var mediaPeriod: MediaPeriod?, var mediaPeriod: MediaPeriod?,
var mediaSourceCaller: MediaSourceCaller?
) )
private class MetadataJob(val data: JobData, val mediaSourceCaller: MediaSourceCaller)
init { init {
mediaSourceThread.start() mediaSourceThread.start()
mediaSourceHandler = Clock.DEFAULT.createHandler(mediaSourceThread.looper, this) mediaSourceHandler = Clock.DEFAULT.createHandler(mediaSourceThread.looper, this)
} }
fun push(uri: Uri): ListenableFuture<TrackGroupArray> { fun retrieve(uri: Uri): ListenableFuture<TrackGroupArray> {
val job = job val job = job
check(job == null || job.future.isDone) { "Already working on something: $job" } check(job == null || job.data.params.future.isDone) { "Already working on something: $job" }
val future = SettableFuture.create<TrackGroupArray>() val future = SettableFuture.create<TrackGroupArray>()
this.job = MetadataJob(MediaItem.fromUri(uri), future, null, null, null) mediaSourceHandler.obtainMessage(MESSAGE_PREPARE, JobParams(uri, future)).sendToTarget()
mediaSourceHandler.sendEmptyMessage(MESSAGE_CHECK_JOBS)
return future return future
} }
fun stop() { fun release() {
mediaSourceHandler.sendEmptyMessage(MESSAGE_RELEASE_ALL) mediaSourceHandler.removeCallbacksAndMessages(null)
mediaSourceThread.quit()
} }
override fun handleMessage(msg: Message): Boolean { override fun handleMessage(msg: Message): Boolean {
when (msg.what) { when (msg.what) {
MESSAGE_CHECK_JOBS -> { MESSAGE_PREPARE -> {
// L.d("checking jobs") val params = msg.obj as JobParams
val job = job
if (job != null) {
val currentMediaSource = job.mediaSource
val currentMediaSourceCaller = job.mediaSourceCaller
val mediaSource: MediaSource
val mediaSourceCaller: MediaSourceCaller
if (currentMediaSource != null && currentMediaSourceCaller != null) {
mediaSource = currentMediaSource
mediaSourceCaller = currentMediaSourceCaller
} else {
mediaSource = mediaSourceFactory.createMediaSource(job.mediaItem)
mediaSourceCaller = MediaSourceCaller(job)
mediaSource.prepareSource(
mediaSourceCaller, /* mediaTransferListener= */ null, PlayerId.UNSET)
job.mediaSource = mediaSource
job.mediaSourceCaller = mediaSourceCaller
}
try { val mediaSource =
val mediaPeriod = job.mediaPeriod mediaSourceFactory.createMediaSource(MediaItem.fromUri(params.uri))
if (mediaPeriod == null) { val data = JobData(params, mediaSource, null)
mediaSource.maybeThrowSourceInfoRefreshError() val mediaSourceCaller = MediaSourceCaller(data)
} else { mediaSource.prepareSource(
mediaPeriod.maybeThrowPrepareError() mediaSourceCaller, /* mediaTransferListener= */ null, PlayerId.UNSET)
} job = MetadataJob(data, mediaSourceCaller)
} catch (e: Exception) {
L.e("Failed to extract MediaSource")
L.e(e.stackTraceToString())
job.mediaPeriod?.let(mediaSource::releasePeriod)
mediaSource.releaseSource(mediaSourceCaller)
job.future.setException(e)
}
}
mediaSourceHandler.sendEmptyMessageDelayed( mediaSourceHandler.sendEmptyMessageDelayed(
MESSAGE_CHECK_JOBS, /* delayMs= */ CHECK_INTERVAL_MS) MESSAGE_CHECK_FAILURE, /* delayMs= */ CHECK_INTERVAL_MS)
return true return true
} }
MESSAGE_CONTINUE_LOADING -> { MESSAGE_CONTINUE_LOADING -> {
checkNotNull((msg.obj as MetadataJob).mediaPeriod) val job = job ?: return true
checkNotNull(job.data.mediaPeriod)
.continueLoading(LoadingInfo.Builder().setPlaybackPositionUs(0).build()) .continueLoading(LoadingInfo.Builder().setPlaybackPositionUs(0).build())
return true return true
} }
MESSAGE_RELEASE -> { MESSAGE_CHECK_FAILURE -> {
val job = msg.obj as MetadataJob val job = job ?: return true
job.mediaPeriod?.let { job.mediaSource?.releasePeriod(it) } val mediaPeriod = job.data.mediaPeriod
job.mediaSourceCaller?.let { job.mediaSource?.releaseSource(it) } val mediaSource = job.data.mediaSource
this.job = null val mediaSourceCaller = job.mediaSourceCaller
try {
if (mediaPeriod == null) {
mediaSource.maybeThrowSourceInfoRefreshError()
} else {
mediaPeriod.maybeThrowPrepareError()
}
} catch (e: Exception) {
L.e("Failed to extract MediaSource")
L.e(e.stackTraceToString())
mediaPeriod?.let(mediaSource::releasePeriod)
mediaSource.releaseSource(mediaSourceCaller)
job.data.params.future.setException(e)
}
return true return true
} }
MESSAGE_RELEASE_ALL -> { MESSAGE_RELEASE -> {
val job = job val job = job ?: return true
if (job != null) { val mediaPeriod = job.data.mediaPeriod
job.mediaPeriod?.let { job.mediaSource?.releasePeriod(it) } val mediaSource = job.data.mediaSource
job.mediaSourceCaller?.let { job.mediaSource?.releaseSource(it) } val mediaSourceCaller = job.mediaSourceCaller
} mediaPeriod?.let { mediaSource.releasePeriod(it) }
mediaSourceHandler.removeCallbacksAndMessages(/* token= */ null) mediaSource.releaseSource(mediaSourceCaller)
mediaSourceThread.quit()
this.job = null this.job = null
return true return true
} }
@ -246,10 +239,11 @@ private class ChunkedMetadataRetriever(private val mediaSourceFactory: MediaSour
} }
} }
private inner class MediaSourceCaller(private val job: MetadataJob) : private inner class MediaSourceCaller(private val data: JobData) :
MediaSource.MediaSourceCaller { MediaSource.MediaSourceCaller {
private val mediaPeriodCallback: MediaPeriodCallback = MediaPeriodCallback(job) private val mediaPeriodCallback: MediaPeriodCallback =
MediaPeriodCallback(data.params.future)
private val allocator: Allocator = private val allocator: Allocator =
DefaultAllocator( DefaultAllocator(
/* trimOnReset= */ true, /* trimOnReset= */ true,
@ -268,20 +262,21 @@ private class ChunkedMetadataRetriever(private val mediaSourceFactory: MediaSour
MediaSource.MediaPeriodId(timeline.getUidOfPeriod(/* periodIndex= */ 0)), MediaSource.MediaPeriodId(timeline.getUidOfPeriod(/* periodIndex= */ 0)),
allocator, allocator,
/* startPositionUs= */ 0) /* startPositionUs= */ 0)
job.mediaPeriod = mediaPeriod data.mediaPeriod = mediaPeriod
mediaPeriod.prepare(mediaPeriodCallback, /* positionUs= */ 0) mediaPeriod.prepare(mediaPeriodCallback, /* positionUs= */ 0)
} }
private inner class MediaPeriodCallback(private val job: MetadataJob) : private inner class MediaPeriodCallback(
MediaPeriod.Callback { private val future: SettableFuture<TrackGroupArray>
) : MediaPeriod.Callback {
override fun onPrepared(mediaPeriod: MediaPeriod) { override fun onPrepared(mediaPeriod: MediaPeriod) {
job.future.set(mediaPeriod.getTrackGroups()) future.set(mediaPeriod.getTrackGroups())
mediaSourceHandler.obtainMessage(MESSAGE_RELEASE, job).sendToTarget() mediaSourceHandler.sendEmptyMessage(MESSAGE_RELEASE)
} }
@Override @Override
override fun onContinueLoadingRequested(source: MediaPeriod) { override fun onContinueLoadingRequested(source: MediaPeriod) {
mediaSourceHandler.obtainMessage(MESSAGE_CONTINUE_LOADING, job).sendToTarget() mediaSourceHandler.sendEmptyMessage(MESSAGE_CONTINUE_LOADING)
} }
} }
} }