music: try to fix extractor thread starvation
This commit is contained in:
parent
ffaff6f08e
commit
d52e301751
1 changed files with 68 additions and 73 deletions
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue