Open brahmkshatriya opened 3 weeks ago
But when using this, it gives me this exception
UnrecognizedInputFormatException: None of the available extractors (FlvExtractor, FlacExtractor, WavExtractor, FragmentedMp4Extractor, Mp4Extractor, AmrExtractor, PsExtractor, OggExtractor, TsExtractor, MatroskaExtractor, AdtsExtractor, Ac3Extractor, Ac4Extractor, Mp3Extractor, AviExtractor, JpegExtractor, PngExtractor, WebpExtractor, BmpExtractor, HeifExtractor) could read the stream.{contentIsMalformed=false, dataType=1}
This sounds expected to me. You effectively asked the library to try and work out what type of file an empty byte array represents, and it couldn't. How could it? There's no signal in empty bytes...
I don't think heading down the path of a DataSource
that returns zero bytes is the right way to approach your problem.
I think what you want is a MediaSource
that doesn't publish any tracks (i.e. work at a different level of abstraction), and then merge that with one that does.
@tonihei might have some more thoughts.
I think what you want is a MediaSource that doesn't publish any tracks (i.e. work at a different level of abstraction), and then merge that with one that does.
There seem to be two different problems here if I understand the requirement correctly.
SingleSampleMediaSource/MediaPeriod
and remove the single sample to make it a NoSampleMediaSource
. As @icbaker already highlighted, creating a zero byte data source won't work unfortunately. Taking a step back though - what do you need this empty video track for exactly? You said something about trying to merge audio and video sources separately, so I'm wondering if there is a completely different approach to your actual problem.
Taking a step back though - what do you need this empty video track for exactly? You said something about trying to merge audio and video sources separately, so I'm wondering if there is a completely different approach to your actual problem.
This is what I want to do
I am successful to do it, if the audio uri only contains audio data and if video exists and only contains video data, Ideally, I would like to create a MediaSource that handles to merge both audio from audio uri and video from video uri
Currently I am passing a custom data class to contain the audio and video uri inside the resolved dataspec, which is then used by both audio source and the video source.
I think what you want is a MediaSource that doesn't publish any tracks (i.e. work at a different level of abstraction), and then merge that with one that does.
Yes, I think that would be the appropriate solution, instead of just sending an empty datasource
- If source A has both audio and video, source B should ideally not even exist or not publish any tracks at all. This could be done as a MediaSource that publishes an empty list of tracks, but if generating the source with no data is cheap, I wouldn't even bother and just always create the placeholder source B. Then you can instruct the track selection process to always prefer the first one in the list if that's not already happening by default.
- If source A has only audio, source B needs to generate an empty list of video samples. I'm not aware of a utility to do this easily at the moment. You could probably copy SingleSampleMediaSource/MediaPeriod and remove the single sample to make it a NoSampleMediaSource. As @icbaker already highlighted, creating a zero byte data source won't work unfortunately.
I looked through the SingleSampleMediaSource & SingleSampleMediaPeriod classes, I would like to when they receive the resolved dataspec? but I guess this is not the ideal solution for me either, since it does not ignore the audio from video source or video from the audio source
Thanks for the drawing!
Ignoring the DataSpec
resolution for a second, the merging logic can probably be done by:
new MergingMediaSource(
new FilteringMediaSource(audioSource, C.TRACK_TYPE_AUDIO),
new FilteringMediaSource(audioSource, C.TRACK_TYPE_VIDEO))
The FilteringMediaSource
makes sure to only publish tracks of the given type, ignoring all other tracks if they exist.
For the part about resolving the URL - how dynamic is this resolution? That is, does it have to happen immediately before playback, or is the result always the same anyway?
MergingMediaSource
as above, or just the audioSource
as needed.CompositeMediaSource<Void>
that resolves the URL first and then creates the wrapped source:private static final class DelayedSource extends CompositeMediaSource<Void> {
private final MediaItem mediaItem;
private MediaSource actualSource;
public DelayedSource(MediaItem mediaItem) {
this.mediaItem = mediaItem;
}
@Override
protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
// Start URL resolution (needs a background thread, e.g. using Loader class)
}
private void onUrlResolved(Uri audioUrl, @Nullable Uri videoUrl) {
actualSource = /* create MergingMediaSource or just audio source here */;
prepareChildSource(null, actualSource);
}
@Override
protected void onChildSourceInfoRefreshed(Void childSourceId, MediaSource mediaSource,
Timeline newTimeline) {
refreshSourceInfo(newTimeline);
}
@Override
public MediaItem getMediaItem() {
return mediaItem;
}
@Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
return actualSource.createPeriod(id, allocator, startPositionUs);
}
@Override
public void releasePeriod(MediaPeriod mediaPeriod) {
return actualSource.createPeriod(mediaPeriod);
}
}
If that is a useful utility class, we can also consider adding it to the library.
Thank you @tonihei , this was very helpful to know!
What If new metadata of the mediaItem is resolved, I cant just use player.replaceMediaItem()
here, because it will cause the player reload the mediaSource infinitely, so where should I notify the player that media item has been updated, since it does not automatically detect it.
@tonihei turns out it does change the mediaItem, if it changes data, but now once in a while, I get this error
java.lang.IllegalStateException
at androidx.media3.common.util.Assertions.checkStateNotNull(Assertions.java:117)
at androidx.media3.exoplayer.source.BaseMediaSource.getPlayerId(BaseMediaSource.java:185)
at androidx.media3.exoplayer.source.CompositeMediaSource.prepareChildSource(CompositeMediaSource.java:122)
at example.DelayedSource.access$prepareChildSource(DelayedSource.kt:43)
at example.DelayedSource$onUrlResolved$2.invokeSuspend(DelayedSource.kt:81)
I dont exactly know what causes it, If it helps this is how I am using the DelayedSource
@OptIn(UnstableApi::class)
class DelayedSource(
private val mediaItem: MediaItem,
private val scope: CoroutineScope,
private val audioFactory: MediaFactories,
private val videoFactory: MediaFactories,
) : CompositeMediaSource<Nothing>() {
private var resolvedMediaItem: MediaItem? = null
private lateinit var actualSource: MediaSource
override fun prepareSourceInternal(mediaTransferListener: TransferListener?) {
super.prepareSourceInternal(mediaTransferListener)
scope.launch(Dispatchers.IO) {
val new = resolve(mediaItem)
onUrlResolved(new)
}
}
private suspend fun onUrlResolved(new: MediaItem) = withContext(Dispatchers.Main) {
resolvedMediaItem = new
val video = new.video
val source = when (val video = new.video) {
null -> null
is Streamable.Media.WithVideo.WithAudio -> videoFactory.create(new)
is Streamable.Media.WithVideo.Only -> if (!video.looping) MergingMediaSource(
FilteringMediaSource(videoFactory.create(new), C.TRACK_TYPE_VIDEO),
FilteringMediaSource(audioFactory.create(new), C.TRACK_TYPE_AUDIO)
) else null
}
actualSource = source ?: FilteringMediaSource(audioFactory.create(new), C.TRACK_TYPE_AUDIO)
prepareChildSource(null, actualSource)
}
override fun getMediaItem() = resolvedMediaItem ?: mediaItem
override fun createPeriod(
id: MediaSource.MediaPeriodId, allocator: Allocator, startPositionUs: Long
) = actualSource.createPeriod(id, allocator, startPositionUs)
override fun releasePeriod(mediaPeriod: MediaPeriod) =
actualSource.releasePeriod(mediaPeriod)
override fun onChildSourceInfoRefreshed(
childSourceId: Nothing?, mediaSource: MediaSource, newTimeline: Timeline
) = refreshSourceInfo(newTimeline)
private suspend fun resolve(mediaItem: MediaItem): MediaItem {
//...
}
}
Edit: You said to use a Loader Class, I dont exactly know what you meant by that, but I implemented this using a kotlin coroutine scope
Also doing something like this, didnt use to cause the mediaItem to be loaded again
val newItem = item.run {
buildUpon().setMediaMetadata(
mediaMetadata.buildUpon().setUserRating(ThumbRating(liked)).build()
)
}.build()
player.replaceMediaItem(session.player.currentMediaItemIndex, newItem)
But after shifting to the DelayedSource, it reloads the whole thing again, how can I prevent it?
Edit : I fixed it by adding the following lines to the DelayedSource
override fun canUpdateMediaItem(mediaItem: MediaItem) =
actualSource.canUpdateMediaItem(mediaItem)
override fun updateMediaItem(mediaItem: MediaItem) =
actualSource.updateMediaItem(mediaItem)
androidx.media3.exoplayer.ExoPlaybackException: Unexpected runtime error
at androidx.media3.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:720)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loopOnce(Looper.java:257)
at android.os.Looper.loop(Looper.java:368)
at android.os.HandlerThread.run(HandlerThread.java:67)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void androidx.media3.exoplayer.hls.playlist.DefaultHlsPlaylistTracker$MediaPlaylistBundle.maybeThrowPlaylistRefreshError()' on a null object reference
at androidx.media3.exoplayer.hls.playlist.DefaultHlsPlaylistTracker.maybeThrowPlaylistRefreshError(DefaultHlsPlaylistTracker.java:225)
at androidx.media3.exoplayer.hls.playlist.DefaultHlsPlaylistTracker.maybeThrowPrimaryPlaylistRefreshError(DefaultHlsPlaylistTracker.java:219)
at androidx.media3.exoplayer.hls.HlsMediaSource.maybeThrowSourceInfoRefreshError(HlsMediaSource.java:510)
at androidx.media3.exoplayer.source.CompositeMediaSource.maybeThrowSourceInfoRefreshError(CompositeMediaSource.java:61)
at androidx.media3.exoplayer.source.CompositeMediaSource.maybeThrowSourceInfoRefreshError(CompositeMediaSource.java:61)
at androidx.media3.exoplayer.source.MergingMediaSource.maybeThrowSourceInfoRefreshError(MergingMediaSource.java:198)
at androidx.media3.exoplayer.source.CompositeMediaSource.maybeThrowSourceInfoRefreshError(CompositeMediaSource.java:61)
Also since I switched, I keep getting this error, from time to time
I am trying to merge audio and video sources seperately using
MergingMediaSource()
, my problem is that I can only know if my mediaItem has a video or not when the mediaItem has been resolved, so I cannot create a MediaSource after the mediaItem has been resolved. Which lead me to think, since I cannot know if i will have just audio or both audio and video, i should always create 2 mediaSources, if the video doesnt exist , then I can just make it send empty data.So my question is, is there a way to send empty data to via the DataSourceFactory. I thought it could be done via doing this
But when using this, it gives me this exception
UnrecognizedInputFormatException: None of the available extractors (FlvExtractor, FlacExtractor, WavExtractor, FragmentedMp4Extractor, Mp4Extractor, AmrExtractor, PsExtractor, OggExtractor, TsExtractor, MatroskaExtractor, AdtsExtractor, Ac3Extractor, Ac4Extractor, Mp3Extractor, AviExtractor, JpegExtractor, PngExtractor, WebpExtractor, BmpExtractor, HeifExtractor) could read the stream.{contentIsMalformed=false, dataType=1}
This is my CustomMediaSourceFactory
So how do I approach this? is there a way to send empty data from a MediaSource? or is there a better way to approach this?