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

Playing simultaneous tracks with media 3 #1473

Closed
zoomGitS opened this issue Jun 19, 2024 · 5 comments
Closed

Playing simultaneous tracks with media 3 #1473

zoomGitS opened this issue Jun 19, 2024 · 5 comments
Assignees
Labels

Comments

@zoomGitS
Copy link

Hi, I want to play multiple audio tracks simultaneously. I know that we can use multiple ExoPlayer instances, but I prefer using a single instance and customizing it to play multiple audio files. I found some information on how to achieve this, but it seems to apply to older versions of ExoPlayer. The solution suggested modifying the RenderersFactory set on the player to include multiple audio renderers, and adjusting the TrackSelector to assign each audio track to a different renderer.

However, this approach doesn't seem to work with the new Media3 library. Can someone help me with this question? Thanks!

@tonihei
Copy link
Collaborator

tonihei commented Jun 19, 2024

If you are referring to comment like this one, I think the approach should still work as the underlying code hasn't changed in any significant ways since.

There is also ongoing work to allow proper audio mixing for playback of multiple tracks, but this may not be available soon.

Could you clarify in more detail what exactly isn't working and maybe we can point you in the right direction?

@tonihei tonihei self-assigned this Jun 19, 2024
@zoomGitS
Copy link
Author

zoomGitS commented Jun 19, 2024

//initilize

   private val exoPlayer by lazy {
        ExoPlayer.Builder(this, renderersFactory).setTrackSelector(trackSelector).build()
    }

//create media sources

val audioThreeFile = File(File(filesDir, "audio_test"), "audio_one_trimed.mp3")
        val audioFourFile = File(File(filesDir, "audio_test"), "auudio_two_trimed.mp3")

        val defaultDataSourceFactory = DefaultDataSource.Factory(applicationContext)

        val sourceOne = ProgressiveMediaSource.Factory(defaultDataSourceFactory)
            .createMediaSource(MediaItem.fromUri(Uri.fromFile(audioThreeFile)))
        val sourceTwo = ProgressiveMediaSource.Factory(defaultDataSourceFactory)
            .createMediaSource(MediaItem.fromUri(Uri.fromFile(audioFourFile)))

        val mergingMediaSource = MergingMediaSource(true, sourceOne, sourceTwo)


        exoPlayer.setMediaSource(mergingMediaSource)
        exoPlayer.prepare()
        exoPlayer.playWhenReady = true

//create renderersFactory

private val renderersFactory = object : DefaultRenderersFactory(this) {
        override fun buildAudioRenderers(
            context: Context,
            extensionRendererMode: Int,
            mediaCodecSelector: MediaCodecSelector,
            enableDecoderFallback: Boolean,
            audioSink: AudioSink,
            eventHandler: Handler,
            eventListener: AudioRendererEventListener,
            out: ArrayList<Renderer>
        ) {
            super.buildAudioRenderers(
                context,
                extensionRendererMode,
                mediaCodecSelector,
                enableDecoderFallback,
                audioSink,
                eventHandler,
                eventListener,
                out
            )
            out.add(AudioRendererWithoutClock(context, mediaCodecSelector))
            out.add(AudioRendererWithoutClock(context, mediaCodecSelector))
            out.add(AudioRendererWithoutClock(context, mediaCodecSelector))
        }
    }

//create trackSelector

private val trackSelector = object : TrackSelector() {
        override fun selectTracks(
            rendererCapabilities: Array<out RendererCapabilities>,
            trackGroups: TrackGroupArray,
            periodId: MediaSource.MediaPeriodId,
            timeline: Timeline
        ): TrackSelectorResult {
            val audioRenderers: Queue<Int> = ArrayDeque()
            val configs = arrayOfNulls<RendererConfiguration>(rendererCapabilities.size)
            val selections = arrayOfNulls<ExoTrackSelection>(rendererCapabilities.size)
            for (i in rendererCapabilities.indices) {
                if (rendererCapabilities[i].trackType == C.TRACK_TYPE_AUDIO) {
                    audioRenderers.add(i)
                    configs[i] = RendererConfiguration.DEFAULT
                }
            }
            for (i in 0 until trackGroups.length) {
                if (MimeTypes.isAudio(trackGroups[i].getFormat(0).sampleMimeType)) {
                    val index = audioRenderers.poll()
                    if (index != null) {
                        selections[index] = FixedTrackSelection(trackGroups[i], 0)
                    }
                }
            }
            return TrackSelectorResult(configs, selections, Any())
        }

        override fun onSelectionActivated(info: Any?) {

        }
    }

//create AudioRenderer

final class AudioRendererWithoutClock extends MediaCodecAudioRenderer {
    public AudioRendererWithoutClock(Context context, MediaCodecSelector mediaCodecSelector) {
        super(context, mediaCodecSelector);
    }

    @Override
    public MediaClock getMediaClock() {
        return null;
    }
}

But getting this error-

git_hub_error

@zoomGitS
Copy link
Author

zoomGitS commented Jun 19, 2024

Maybe the issue lies in the RenderersFactory where I am adding three new AudioRendererWithoutClock instances. However, I actually only need one additional AudioRendererWithoutClock to render the second audio.

@tonihei
Copy link
Collaborator

tonihei commented Jun 20, 2024

Thanks for highlighting the problem! This is indeed related to the additional renderers. Having more renderers than needed is not usually an issue, but there is a bug in the TrackSelector code I provided in the GitHub issue. The configs[i] = RendererConfiguration.DEFAULT line needs to move further down to where we decide which renderer to use. I fixed my original post in google/ExoPlayer#6589 (comment) too to avoid further confusion. Note that this is still just an example though and it wouldn't handle other renderer types like video.

copybara-service bot pushed a commit that referenced this issue Jun 24, 2024
The two arrays need to have the same length and the selection
must match in their nullness (unless for TYPE_NONE
renderers). Clarify this more clearly in the docs and add
new asssertions for it. This avoids that the player is failing
in obscure ways much later.

Issue: #1473
#cherrypick
PiperOrigin-RevId: 646086833
tianyif pushed a commit that referenced this issue Jul 2, 2024
The two arrays need to have the same length and the selection
must match in their nullness (unless for TYPE_NONE
renderers). Clarify this more clearly in the docs and add
new asssertions for it. This avoids that the player is failing
in obscure ways much later.

Issue: #1473
#cherrypick
PiperOrigin-RevId: 646086833
(cherry picked from commit 71ef848)
@tonihei
Copy link
Collaborator

tonihei commented Aug 28, 2024

Also added the fail-early checks in the commits above. Closing the issue.

@tonihei tonihei closed this as completed Aug 28, 2024
@androidx androidx locked and limited conversation to collaborators Oct 28, 2024
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

2 participants