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

Problem with codec re-initialization during HLS MP3 playback #5063

Closed
Curwer-zz opened this issue Nov 6, 2018 · 7 comments
Closed

Problem with codec re-initialization during HLS MP3 playback #5063

Curwer-zz opened this issue Nov 6, 2018 · 7 comments
Assignees
Labels

Comments

@Curwer-zz
Copy link

Curwer-zz commented Nov 6, 2018

Issue description

We use exoPlayer for audio playback, but we have a problem with where the audio codec gets released and just milliseconds later it initialized again. This creates a short but noticed "cut" in the audio. When looking in profiler we can tell that something is happening. i´ll attach a link to a screenshot of the profiler output.
This is what it looks like when we init the player ..

if (bookChanged || exoPlayer == null) {
            release(false)

            if (exoPlayer == null) {
                exoPlayer = ExoPlayerFactory.newSimpleInstance(context, DefaultTrackSelector(AdaptiveTrackSelection.Factory(DefaultBandwidthMeter())))
                exoPlayer?.addListener(exoPlayerEventListener)
            }

//            exoPlayer?.audioStreamType = AudioManager.STREAM_VOICE_CALL
            exoPlayer?.audioAttributes = AudioAttributes.Builder().setContentType(C.CONTENT_TYPE_SONIFICATION).setUsage(C.USAGE_MEDIA).build()
            exoPlayer?.playWhenReady = playWhenReady
            // exoPlayer?.playbackParameters = PlaybackParameters(speed, 1f) // Not working if set here. Set in onLoadingChanged() instead
            playOnFocusGain = playWhenReady

            if (position != null && position > 0) seekToPositionOnPlay = position * 1000L

            exoPlayer?.prepare(getMediaSource(book)) // Triggers onPlayerStateChanged when the stream is ready to play

            if (seekToPositionOnPlay != null) {
                exoPlayer?.seekTo(seekToPositionOnPlay!!)
                seekToPositionOnPlay = null
            } else {
                exoPlayer?.seekTo(0)
            }

            callback?.onPlaybackStatusChanged(state)

            wifiLock.acquire()
        }
`

private fun getMediaSource(book: Book): MediaSource {
        return if (offlineHelper.getCachedBooks.contains(book.isbn)) {
            val file = File(FileUtils.getDirectory(context.filesDir, FileUtils.OFFLINE_MP3), "${book.isbn}.mp3")
            Timber.e("playing mp3")
            ExtractorMediaSource
                    .Factory(DefaultDataSourceFactory(context, userAgent, null))
                    .createMediaSource(Uri.parse(file.toString()))
        } else {
            Timber.e("playing HLS")
            HlsMediaSource
                    .Factory(HlsDataSourceFactory { offlineHelper.dataSourceFactory?.createDataSource() })
                    .setPlaylistParser { uri, inputStream ->
                        val playlist = HlsPlaylistParser().parse(uri, inputStream)
                        playlist
                    }
                    .createMediaSource(Uri.parse(book.streamUrl))
        }

Reproduction steps

You can try with this link right here.
http://delivery-42-lb1.elib.se/library/v4/mp3:http/2f0d8302-ca3f-4f8c-9c89-1f18c4e1df06.mp3/playlist.m3u8?StreamSession=79cb6038-9a03-467c-9193-a6a466f547c2&validate=3ca0f7b4436f43b5c99006788f0629cf

For us it´s enough just to play the stream, this happens on both our app, and the a different app that the company providing the links wrote, also based on exoPlayer.

Link to test content

http://delivery-42-lb1.elib.se/library/v4/mp3:http/2f0d8302-ca3f-4f8c-9c89-1f18c4e1df06.mp3/playlist.m3u8?StreamSession=79cb6038-9a03-467c-9193-a6a466f547c2&validate=3ca0f7b4436f43b5c99006788f0629cf

Version of ExoPlayer being used

2.7.0 (also tried to bump to 2.9.0 with no luck)

Device(s) and version(s) of Android being used

Samsung galaxy s8+ / s7 both running 8.0.0
Huawei Honor 8 android 7.0
Google Pixel 2XL runnig 9.0.0

A full bug report captured from the device

I have sent you a mail with the profile trace session we captured.

@AquilesCanta
Copy link
Contributor

@ojw28 can you have a look at this in the context of codec reuse?

@ojw28
Copy link
Contributor

ojw28 commented Nov 7, 2018

I think there's a problem with the order in which you're doing things. Your code looks like it's:

  1. Setting playWhenReady = true
  2. Calling prepare
  3. Calling seekTo

After step (2), playback will start as soon as it can from the default position, because that's what you asked for in step (1). Step (3) will then perform a seek. If the player prepares itself quickly enough then you may hear a short amount of audio between the player processing (2) and (3), followed by a cut when it processes (3), followed by more audio after (3).

You'll probably find that moving playWhenReady = true to the end removes the audible cut, but it may not be as efficient as it could be. A better solution is to call seekTo first, then prepare(source, /* resetPosition= */ false, true) second. This means the player can just prepare from the correct position to start with. Note the three-arg version of prepare should be used to retain the seek position that you've specified. You can set playWhenReady = true at any point in this sequence. In a future change we're looking to add prepareFrom methods to prepare from a specific position, since it's quite unintuitive at the moment.

Regarding codec release and re-initialization: This isn't the cause of the audible cut. It's just a symptom of the fact that a seek is occurring after preparation, which you can resolve as described above. This is because we currently re-initialize the audio codec on seek. We're optimizing this in a future release, and indeed if you use the latest dev-v2 branch you should see the same codec instance being flushed re-used. This change will likely arrive in 2.10.x.

@ojw28 ojw28 added the question label Nov 7, 2018
@Curwer-zz
Copy link
Author

@ojw28
Thanks I changed our code to look like what you described.
But I think you missunderstood the problem we have.

It´s not that is a jump/lag in the begining of the stream. It happens at the end of every chunk, that is every 10 seconds.

I just confirmed that it´s still a problem after changing our code.

@ojw28
Copy link
Contributor

ojw28 commented Nov 8, 2018

I see. It looks like we think the format of each segment is different, due to the presence of an com.apple.streaming.transportStreamTimestamp ID3 tag in each segment with a different value. This ends up causing the decoder to be re-instantiated (or just flushed in the dev-v2 branch, but that's still a problem).

In dev-v2 we correctly see that we can re-use the decoder, but we're not disambiguating between within-the-same-stream (in which case we can presumably do nothing at all to the decoder) and one-stream-to-the-next (which does require a flush). @andrewlewis - Thoughts?

An alternative would be to modify the HLS module to not output formats if only the metadata is changing. This might be a better short term fix. @AquilesCanta - Thoughts?

@ojw28 ojw28 added bug and removed question labels Nov 8, 2018
@ojw28 ojw28 changed the title Problem with codec during HLS playback Problem with codec re-initialization during HLS MP3 playback Nov 8, 2018
@Curwer-zz

This comment has been minimized.

@mihaaxiell

This comment has been minimized.

ojw28 added a commit that referenced this issue Nov 22, 2018
The input.getLength() check is invalid because the length may be
unknown (i.e. if the server doesn't include a Content-Length
response header when serving chunks).

Issue: #5063 (tangentially related only)

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=222406347
andrewlewis pushed a commit that referenced this issue Nov 27, 2018
Issue: #5063
PiperOrigin-RevId: 222975020
@ojw28
Copy link
Contributor

ojw28 commented Nov 27, 2018

This should be fixed in the dev-v2 branch.

@ojw28 ojw28 closed this as completed Nov 27, 2018
ojw28 added a commit that referenced this issue Nov 28, 2018
The input.getLength() check is invalid because the length may be
unknown (i.e. if the server doesn't include a Content-Length
response header when serving chunks).

Issue: #5063 (tangentially related only)

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=222406347
ojw28 added a commit that referenced this issue Nov 28, 2018
Issue: #5063
PiperOrigin-RevId: 222975020
@google google locked and limited conversation to collaborators May 16, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

4 participants