Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ExoPlaybackException: Source error #8586

Closed
badrazizi opened this issue Feb 14, 2021 · 17 comments
Closed

ExoPlaybackException: Source error #8586

badrazizi opened this issue Feb 14, 2021 · 17 comments
Assignees

Comments

@badrazizi
Copy link

badrazizi commented Feb 14, 2021

reported by multiple user on different device and android version sudden stop which happen completely random with mp4 stream at different time. When try to resume, video resume fine, but some time there no audio or get corrupted this error randomly happen with different stream source, which did not happen with the old version ( 2.8.3 ) after updating to version ( 2.12.2 ) more than a month ago, I start seeing this error in the log that user report.

I tried to reproduce it but only happened once after multiple tries with the same error.
Link used http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4.

Stack Trace

com.google.android.exoplayer2.ExoPlaybackException: Source error
	at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(SourceFile:41)
	at android.os.Handler.dispatchMessage(Handler.java:103)
	at android.os.Looper.loop(Looper.java:214)
	at android.os.HandlerThread.run(HandlerThread.java:67)
Caused by: v.e.b.b.c1: Invalid NAL length
	at com.google.android.exoplayer2.extractor.mp4.Mp4Extractor.isSeekable(SourceFile:52)
                                                             lambda$processMoovAtom$1
                                                             readSample
                                                             read
	at com.google.android.exoplayer2.source.ProgressiveMediaPeriod$ExtractingLoadable.com.google.android.exoplayer2.source.BundledExtractorsAdapter.read(SourceFile:47)
                                                                                   load
	at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(SourceFile:6)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
	at java.lang.Thread.run(Thread.java:919)

Code

private fun initPlayer() {
    val bandwidthMeter = DefaultBandwidthMeter.Builder(this).build()
    val videoTrackSelectionFactory: TrackSelection.Factory = AdaptiveTrackSelection.Factory()
    val trackSelector = DefaultTrackSelector(this, videoTrackSelectionFactory)
    val allocator = DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE)
    val loadControl = DefaultLoadControl.Builder()
        .setAllocator(allocator)
        .setTargetBufferBytes(C.LENGTH_UNSET)
        .setBufferDurationsMs(10000, bufferSize, 1000, 1000)
        .setPrioritizeTimeOverSizeThresholds(true)
        .build()
    val renderersFactory = DefaultRenderersFactory(this)
    val clock = Clock.DEFAULT
    val analyticsCollector = AnalyticsCollector(clock)

    player = SimpleExoPlayer
        .Builder(this, renderersFactory, DefaultExtractorsFactory())
        .setTrackSelector(trackSelector)
        .setLoadControl(loadControl)
        .setBandwidthMeter(bandwidthMeter)
        .setLooper(looper)
        .setAnalyticsCollector(analyticsCollector)
        .setHandleAudioBecomingNoisy(true)
        .setSeekParameters(SeekParameters.CLOSEST_SYNC)
        .build()

    player.addListener(playerListener)

    videoView.setControlDispatcher(DefaultControlDispatcher(10000, 10000))
    videoView.controllerShowTimeoutMs = 3000
    videoView.player = player
    videoView.hideController()
}

private fun setSource(link: String) {
    val file = Uri.parse(link)
    val defaultDataSourceFactory = CacheDataSourceFactory(this)
    val videoMediaItem = MediaItem.Builder().setUri(file)

    val videoSource = ProgressiveMediaSource.Factory(defaultDataSourceFactory).setLoadErrorHandlingPolicy(DefaultLoadErrorHandlingPolicy(16)).createMediaSource(videoMediaItem.build())

    player.run {
        setMediaSource(videoSource)
        play()
        prepare()
    }
}

Cache

lateinit var simpleCacheInstance: SimpleCache

class CustomSimpleCache {
    companion object {
        fun getInstance(context: Context, maxCacheSize: Long): SimpleCache {
            if (!::simpleCacheInstance.isInitialized) {
                val evictor = LeastRecentlyUsedCacheEvictor(maxCacheSize)
                val databaseProvider: DatabaseProvider = ExoDatabaseProvider(context)
                simpleCacheInstance = SimpleCache(File(context.cacheDir, "media"), evictor, databaseProvider)
            }
            return simpleCacheInstance
        }
    }
}

class CacheDataSourceFactory(
    private val context: Context,
    headers: HashMap<String, Any> = hashMapOf(),
    userAgent: String? = null,
    private val maxCacheSize: Long = 10485760,
    private val maxFileSize: Long = 10485760
) : DataSource.Factory, Call.Factory {

    private val defaultDatasourceFactory: DefaultDataSourceFactory

    init {
        val bandwidthMeter = DefaultBandwidthMeter.Builder(context).build()
        val client = OkHttpDataSourceFactory(this, userAgent)

        val dataSource = client.createDataSource()

        for (header in headers) {
            dataSource.setRequestProperty(header.key, header.value.toString())
        }

        defaultDatasourceFactory = DefaultDataSourceFactory(
            this.context,
            bandwidthMeter,
            client
        )


    }

    override fun newCall(request: Request): Call {
        val client = getOkHttpClient(context)
        return client.newCall(request)
    }

    override fun createDataSource(): DataSource {
        val cache = CustomSimpleCache.getInstance(context, maxCacheSize)
        return CacheDataSource(
            cache,
            defaultDatasourceFactory.createDataSource(),
            FileDataSource(),
            CacheDataSink(cache, maxFileSize),
            CacheDataSource.FLAG_BLOCK_ON_CACHE or CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR,
            null
        )
    }
}
implementation 'com.google.android.exoplayer:exoplayer:2.12.2'
implementation 'com.google.android.exoplayer:extension-okhttp:2.12.2'
implementation 'com.google.android.exoplayer:extension-cast:2.12.2'
  • ExoPlayer version 2.12.2
  • Android versions ( 8 / 9 / 10 )
  • Android device ( xiaomi / motorola / nokia / samsung / huawei )
@badrazizi badrazizi changed the title Source error ExoPlaybackException: Source error Feb 15, 2021
@christosts christosts self-assigned this Feb 16, 2021
@badrazizi
Copy link
Author

I will continue testing without cache in case it's a cache issue, will report soon.

@badrazizi
Copy link
Author

Cache or no cache same error, even upgrading to version 2.13.1 does not solve it.
device used for testing xiaomi mi a1 android 9 (android one).
I will try without okhttp and report back.

@badrazizi
Copy link
Author

same error.
when the error happen if I forward 1 second and then play then everything work as expected

@christosts
Copy link
Contributor

From the stack trace that is posted on the description of the issue, the error is raised from the Mp4Extractor, indicating that for some reason the bit stream is malformed. I tried playing the content with v2.13.0 on a device and did not see the error. Since you are seeing this error arbitrarily, it means that something corrupts the data in a non-deterministic manner, assuming you see the same error every time. Can you please perform the following:

  1. Play the video with the exoplayer demo app. If the error is not reproduced, then this is indicates there might be something in the way the player is setup in your app - maybe there's a bug that is not revealed from the demo app.
  2. (this is a long shot but I have seen this happening). I noticed the link to the test content is using plain HTTP. Can you please switch to using HTTPS and try it in both your app and the exoplayer demo app? HTTP is not robust against corrupted transfers and I have seen cases where malfunctioning NAT boxes introduce errors that are undetectable to the network stack. HTTPS has each own integrity checks and will guard any transfer errors.

@badrazizi
Copy link
Author

Happen with HTTPS/HTTP, I noticed the error happened when I use unstable internet (mobile data), but never happened with ADSL.
Will test the demo app

@christosts
Copy link
Contributor

On your test with the demo app, please make sure to use HTTPS too.

@badrazizi
Copy link
Author

never happened with demo app, I used both HTTPS/HTTP

@badrazizi
Copy link
Author

while i was testing found another error in latest release which cause infinite buffering

DefaultAudioSink: Discontinuity detected [expected 300744829, got 300535850]
DefaultAudioSink: Discontinuity detected [expected 300884149, got 300651972]
DefaultAudioSink: Discontinuity detected [expected 301023492, got 300814512]
DefaultAudioSink: Discontinuity detected [expected 301209251, got 301000249]
DefaultAudioSink: Discontinuity detected [expected 301302108, got 301093129]
DefaultAudioSink: Discontinuity detected [expected 301580748, got 301325351]
DefaultAudioSink: Discontinuity detected [expected 301603991, got 301394988]
DefaultAudioSink: Discontinuity detected [expected 301882607, got 301511088]
DefaultAudioSink: Discontinuity detected [expected 301836167, got 301603968]
DefaultAudioSink: Discontinuity detected [expected 301905827, got 301673650]
DefaultAudioSink: Discontinuity detected [expected 302114830, got 301859410]
DefaultAudioSink: Discontinuity detected [expected 302277369, got 301952267]
DefaultAudioSink: Discontinuity detected [expected 302347006, got 302068367]
DefaultAudioSink: Discontinuity detected [expected 302393446, got 302184489]
DefaultAudioSink: Discontinuity detected [expected 302602449, got 302300589]
DefaultAudioSink: Discontinuity detected [expected 302741768, got 302439886]
DefaultAudioSink: Discontinuity detected [expected 302997165, got 302788208]
DefaultAudioSink: Discontinuity detected [expected 303182947, got 302973945]
DefaultAudioSink: Discontinuity detected [expected 303415124, got 303159727]
DefaultAudioSink: Discontinuity detected [expected 303670566, got 303438344]
DefaultAudioSink: Discontinuity detected [expected 303833083, got 303624104]
  • Device Xiaomi MI A1 Android 9 (android one)
  • Version 2.13.1

@badrazizi
Copy link
Author

It's seems the issue in the load control, I run the video 6 times so far no error on version 2.12.2 without load control

private fun initPlayer() {
    val renderersFactory = DefaultRenderersFactory(this)

    player = SimpleExoPlayer.Builder(this, renderersFactory)
        .setHandleAudioBecomingNoisy(true)
        .build()
}

will continue testing just to make sure

@badrazizi
Copy link
Author

badrazizi commented Feb 21, 2021

With load control, it's happen more frequently.
code to reproduce it.

private fun initPlayer() {
    val allocator = DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE)
    val loadControl = DefaultLoadControl.Builder()
            .setAllocator(allocator)
            .setTargetBufferBytes(C.LENGTH_UNSET)
            .setBufferDurationsMs(10000, 120000, 1000, 1000)
            .setPrioritizeTimeOverSizeThresholds(true)
            .build()
    val renderersFactory = DefaultRenderersFactory(this)

    player = SimpleExoPlayer.Builder(this, renderersFactory)
        .setLoadControl(loadControl)
        .setHandleAudioBecomingNoisy(true)
        .build()
}

Without load control it's happen but very rare, and only happened with me while internet (mobile data) high ping and slow speed

@christosts
Copy link
Contributor

christosts commented Feb 23, 2021

From the information you have provided so far, it looks like the error appears on your app with mobile data and using the custom load control.

To narrow down the problem, can you please try using the following:

  • the demo app, with no changes, using mobile data and ADSL
  • the demo app, using your code snippet for building the LoadControl, on mobile data and ADSL. The demo app creates the player in PlayerActivity.initializePlayer here, you should be able to replace the code creating the player instance with
      DefaultLoadControl defaultLoadControl = new DefaultLoadControl.Builder()
          .setAllocator(new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE))
          .setTargetBufferBytes(C.LENGTH_UNSET)
          .setBufferDurationsMs(10000, 120000, 1000, 1000)
          .setPrioritizeTimeOverSizeThresholds(true)
          .build();
      DefaultRenderersFactory defaultRenderersFactory = new DefaultRenderersFactory(this);

      player = new SimpleExoPlayer.Builder(this, defaultRenderersFactory)
          .setLoadControl(defaultLoadControl)
          .setHandleAudioBecomingNoisy(true)
          .build();

If it happens with the demo app in any of the above scenarios, it would help indicate if it's the mobile data or the custom load control.

update: I have tried with the custom load control of the snippet above on the demo app and have not been able to reproduce.

@badrazizi
Copy link
Author

Sorry for taking too long, here the test result

To narrow down the problem, can you please try using the following:

  • the demo app, with no changes, using mobile data and ADSL

I could not reproduce it without changing anything

  • the demo app, using your code snippet for building the LoadControl, on mobile data and ADSL. The demo app creates the player in PlayerActivity.initializePlayer here, you should be able to replace the code creating the player instance with

the error happened while using mobile data and not with ADSL

changed useragent in

private static final String USER_AGENT =
"ExoPlayerDemo/"
+ ExoPlayerLibraryInfo.VERSION
+ " (Linux; Android "
+ Build.VERSION.RELEASE
+ ") "
+ ExoPlayerLibraryInfo.VERSION_SLASHY;
to

Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0

and changed

private static final boolean USE_CRONET_FOR_NETWORKING = true;
to false

the modified code used in the demo app

  protected boolean initializePlayer() {
    if (player == null) {
      Intent intent = getIntent();

      mediaItems = createMediaItems(intent);
      if (mediaItems.isEmpty()) {
        return false;
      }

      boolean preferExtensionDecoders = intent.getBooleanExtra(IntentUtil.PREFER_EXTENSION_DECODERS_EXTRA, false);
      RenderersFactory renderersFactory = DemoUtil.buildRenderersFactory(this, preferExtensionDecoders);
      MediaSourceFactory mediaSourceFactory = new ProgressiveMediaSource.Factory(dataSourceFactory);

      DefaultLoadControl defaultLoadControl = new DefaultLoadControl.Builder()
          .setAllocator(new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE))
          .setTargetBufferBytes(C.LENGTH_UNSET)
          .setBufferDurationsMs(10000, 120000, 1000, 1000)
          .setPrioritizeTimeOverSizeThresholds(true)
          .build();

      player = new SimpleExoPlayer.Builder(this, new DefaultRenderersFactory(this))
              .setLoadControl(defaultLoadControl)
              .setMediaSourceFactory(mediaSourceFactory)
              .build();

      player.setPlayWhenReady(startAutoPlay);
      playerView.setPlayer(player);
    }
    boolean haveStartPosition = startWindow != C.INDEX_UNSET;
    if (haveStartPosition) {
      player.seekTo(startWindow, startPosition);
    }
    player.setMediaItems(mediaItems, !haveStartPosition);
    player.prepare();
    updateButtonVisibility();
    return true;
  }

stacktrace

2021-03-05 00:06:04.842 10794-11117/com.google.android.exoplayer2.demo E/ExoPlayerImplInternal: Playback error
      com.google.android.exoplayer2.ExoPlaybackException: Source error
        at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:579)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:193)
        at android.os.HandlerThread.run(HandlerThread.java:65)
     Caused by: com.google.android.exoplayer2.ParserException: Invalid NAL length
        at com.google.android.exoplayer2.extractor.mp4.Mp4Extractor.readSample(Mp4Extractor.java:607)
        at com.google.android.exoplayer2.extractor.mp4.Mp4Extractor.read(Mp4Extractor.java:251)
        at com.google.android.exoplayer2.source.BundledExtractorsAdapter.read(BundledExtractorsAdapter.java:127)
        at com.google.android.exoplayer2.source.ProgressiveMediaPeriod$ExtractingLoadable.load(ProgressiveMediaPeriod.java:1048)
        at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:415)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:764)

@christosts
Copy link
Contributor

I'm afraid I wasn't able to reproduce using mobile data too. At this point, the data points we have are:

  • the issue appears with mobile data only and not with wifi/ADSL
  • the issue appears with a custom load control, that compared to the default one, buffers more data
  • in your code snippet you modified the demo app to use the DefaultHttpDataSource (instead of cronet) with a custom agent.

Unfortunately, without a clear reproduction step, we can only offer debugging assistance. Given the data we have so far, my next step of investigation would be to check whether what is being transferred over the mobile data connection is correct. That said, have you been using the same mobile operator in all your tests? Have you tried using a different operator?

A way to verify the downloaded data match the expected file would be to store the file locally as it is being downloaded and, if the error occurs, to compare the downloaded data with the equivalent bytes from the original file. You can store downloaded data by wrapping the DefaultHttpDataSource with a TeeDataSource. Eg, you can update getDataSourceFactory() in DemoUtil as follows (this is from 2.13.2)

/** Returns a {@link DataSource.Factory}. */
  public static synchronized DataSource.Factory getDataSourceFactory(Context context) {
    if (dataSourceFactory == null) {
      context = context.getApplicationContext();
      final Context context1 = context;
      DataSource.Factory teeDataSourceFactory = () ->
          new TeeDataSource(getHttpDataSourceFactory(context1).createDataSource(), new DataSink() {
            private FileOutputStream fos;
            @Override
            public void open(DataSpec dataSpec) throws IOException {             
              fos = new FileOutputStream(new File("{location of your stored file}");
            }

            @Override
            public void write(byte[] buffer, int offset, int length) throws IOException {
              fos.write(buffer, offset, length);
            }

            @Override
            public void close() throws IOException {
              fos.close();
            }
          });
      DefaultDataSourceFactory upstreamFactory =
          new DefaultDataSourceFactory(context, teeDataSourceFactory);
      dataSourceFactory = buildReadOnlyCacheDataSource(upstreamFactory, getDownloadCache(context));
    }
    return dataSourceFactory;
  }

@badrazizi
Copy link
Author

will test that and report back soon

@ojw28
Copy link
Contributor

ojw28 commented Mar 14, 2021

Marking needs more info.

@google-oss-bot
Copy link
Collaborator

Hey @badrazizi. We need more information to resolve this issue but there hasn't been an update in 14 weekdays. I'm marking the issue as stale and if there are no new updates in the next 7 days I will close it automatically.

If you have more information that will help us get to the bottom of this, just add a comment!

@google-oss-bot
Copy link
Collaborator

Since there haven't been any recent updates here, I am going to close this issue.

@badrazizi if you're still experiencing this problem and want to continue the discussion just leave a comment here and we are happy to re-open this.

@google google locked and limited conversation to collaborators Jun 12, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants