Giter Site home page Giter Site logo

androidx / media Goto Github PK

View Code? Open in Web Editor NEW
1.2K 1.2K 276.0 423.83 MB

Jetpack Media3 support libraries for media use cases, including ExoPlayer, an extensible media player for Android

License: Apache License 2.0

Java 99.03% CMake 0.02% C++ 0.49% Shell 0.04% Makefile 0.05% AIDL 0.12% GLSL 0.26%
android exoplayer java mediaplayer

media's Introduction

Android Jetpack

Revved up by Develocity

Jetpack is a suite of libraries, tools, and guidance to help developers write high-quality apps easier. These components help you follow best practices, free you from writing boilerplate code, and simplify complex tasks, so you can focus on the code you care about.

Jetpack comprises the androidx.* package libraries, unbundled from the platform APIs. This means that it offers backward compatibility and is updated more frequently than the Android platform, making sure you always have access to the latest and greatest versions of the Jetpack components.

Our official AARs and JARs binaries are distributed through Google Maven.

You can learn more about using it from Android Jetpack landing page.

Contribution Guide

For contributions via GitHub, see the GitHub Contribution Guide.

Note: The contributions workflow via GitHub is currently experimental - only contributions to the following projects are being accepted at this time:

Code Review Etiquette

When contributing to Jetpack, follow the code review etiquette.

Accepted Types of Contributions

  • Bug fixes - needs a corresponding bug report in the Android Issue Tracker
  • Each bug fix is expected to come with tests
  • Fixing spelling errors
  • Updating documentation
  • Adding new tests to the area that is not currently covered by tests
  • New features to existing libraries if the feature request bug has been approved by an AndroidX team member.

We are not currently accepting new modules.

Checking Out the Code

Head over to the onboarding docs to learn more about getting set up and the development workflow!

Continuous integration

Our continuous integration system builds all in progress (and potentially unstable) libraries as new changes are merged. You can manually download these AARs and JARs for your experimentation.

Password and Contributor Agreement before making a change

Before uploading your first contribution, you will need setup a password and agree to the contribution agreement:

Generate a HTTPS password: https://android-review.googlesource.com/new-password

Agree to the Google Contributor Licenses Agreement: https://android-review.googlesource.com/settings/new-agreement

Getting reviewed

  • After you run repo upload, open r.android.com
  • Sign in into your account (or create one if you do not have one yet)
  • Add an appropriate reviewer (use git log to find who did most modifications on the file you are fixing or check the OWNERS file in the project's directory)

Handling binary dependencies

AndroidX uses git to store all the binary Gradle dependencies. They are stored in prebuilts/androidx/internal and prebuilts/androidx/external directories in your checkout. All the dependencies in these directories are also available from google(), or mavenCentral(). We store copies of these dependencies to have hermetic builds. You can pull in a new dependency using our importMaven tool.

media's People

Contributors

1nsun avatar andrewlewis avatar aquilescanta avatar botaydotcom avatar christosts avatar claincly avatar dburckh avatar dway123 avatar erdemguven avatar hamzah-z avatar hmsch avatar icbaker avatar jaewan-github avatar kim-vde avatar krocard avatar leonwind avatar marcbaechinger avatar microkatz avatar oceanjules avatar ojw28 avatar rohitjoins avatar samrobbo avatar sheenachhabra avatar sim0629 avatar stevemayhew avatar szaboa avatar tianyif avatar tof-tof avatar tonihei avatar ybai001 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

media's Issues

Roadmap?

Hi there!

Can you share a roadmap of the releases? When do you plan to come out of alpha?
Will media3 eventually supersede exoplayer?

MediaControllerStub.onPlayerInfoChanged() takes too much time when having a large playlist especially with multiple MeidaControllers

Conditions

  • Playlist size: 10k items
  • MediaController: 3 instances attached to the MediaSession
  • Xiaomi Mi 11 Lite 5G (Android 12)

Measured result

It took 1,000 ms - 3,000 ms per controller, which means it took 3,000 ms - 9,000 ms totally executed on the main thread.

Stacktraces (where the most of time consumed)

nativeWriteString16:-1, Parcel (android.os)
writeString16NoHelper:870, Parcel (android.os)
writeString16:424, Parcel$ReadWriteHelper (android.os)
writeString16:849, Parcel (android.os)
writeString:839, Parcel (android.os)
writeArrayMapInternal:1022, Parcel (android.os)
writeToParcelInner:1620, BaseBundle (android.os)
writeToParcel:1304, Bundle (android.os)
writeBundle:1092, Parcel (android.os)
writeValue:1849, Parcel (android.os)
writeArrayMapInternal:1023, Parcel (android.os)
writeToParcelInner:1620, BaseBundle (android.os)
writeToParcel:1304, Bundle (android.os)
writeBundle:1092, Parcel (android.os)
onTransact:86, BundleListRetriever (androidx.media3.common)
transact:1067, Binder (android.os)
getList:111, BundleListRetriever (androidx.media3.common)
fromBundleListRetriever:1480, Timeline (androidx.media3.common)
fromBundle:1462, Timeline (androidx.media3.common)
$r8$lambda$IosUR-LlCTyBXD6bufNR07UK3iY:-1, Timeline (androidx.media3.common)
fromBundle:-1, Timeline$$ExternalSyntheticLambda0 (androidx.media3.common)
fromNullableBundle:59, BundleableUtil (androidx.media3.common.util)
fromBundle:825, PlayerInfo (androidx.media3.session)
$r8$lambda$UYUSO7JOpKqYSM-ZlCuW_lSgFks:-1, PlayerInfo (androidx.media3.session)
fromBundle:-1, PlayerInfo$$ExternalSyntheticLambda0 (androidx.media3.session)
onPlayerInfoChanged:173, MediaControllerStub (androidx.media3.session)
onPlayerInfoChanged:1734, MediaSessionStub$Controller2Cb (androidx.media3.session)
dispatchOnPlayerInfoChanged:396, MediaSessionImpl (androidx.media3.session)
access$600:80, MediaSessionImpl (androidx.media3.session)
handleMessage:1194, MediaSessionImpl$PlayerInfoChangedHandler (androidx.media3.session)
dispatchMessage:106, Handler (android.os)
loopOnce:210, Looper (android.os)
loop:299, Looper (android.os)
main:8085, ActivityThread (android.app)
invoke:-1, Method (java.lang.reflect)
run:556, RuntimeInit$MethodAndArgsCaller (com.android.internal.os)
main:1045, ZygoteInit (com.android.internal.os)
newStringFromChars:109, StringFactory (java.lang)
toString:158, Integer (java.lang)
keyForField:1063, MediaMetadata (androidx.media3.common)
toBundle:914, MediaMetadata (androidx.media3.common)
toBundle:1834, MediaItem (androidx.media3.common)
toBundle:459, Timeline$Window (androidx.media3.common)
access$000:151, Timeline$Window (androidx.media3.common)
toBundle:1407, Timeline (androidx.media3.common)
toBundle:738, PlayerInfo (androidx.media3.session)
onPlayerInfoChanged:1736, MediaSessionStub$Controller2Cb (androidx.media3.session)
dispatchOnPlayerInfoChanged:396, MediaSessionImpl (androidx.media3.session)
access$600:80, MediaSessionImpl (androidx.media3.session)
handleMessage:1194, MediaSessionImpl$PlayerInfoChangedHandler (androidx.media3.session)
dispatchMessage:106, Handler (android.os)
loopOnce:210, Looper (android.os)
loop:299, Looper (android.os)
main:8085, ActivityThread (android.app)
invoke:-1, Method (java.lang.reflect)
run:556, RuntimeInit$MethodAndArgsCaller (com.android.internal.os)
main:1045, ZygoteInit (com.android.internal.os)

will there be ffmpeg support ? I saw in eco 2.16 it was removed why ?

Unfortunately we can't answer all questions. Unclear questions or questions with
insufficient information may not get attention.

Before filing a question:

When filing a question:

Describe your question in detail.

In case your question refers to a problem you are seeing in your app:

  • Output of running $ adb bugreport in the console

In case your question is related to a piece of media:

  • URI to test content
  • For protected content:
    • DRM scheme and license server URL
    • Authentication HTTP headers

Don't forget to check ExoPlayer's supported formats and devices, if applicable
(https://exoplayer.dev/supported-formats.html).

If there's something you don't want to post publicly, please submit the issue,
then email the link/bug report to [email protected] using a subject in the
format "Issue #1234", where #1234 is your issue number (we don't reply to
emails).

ExoPlayer (vs Media3) versioning

When migrating from standalone ExoPlayer to this Media3, is there any information on what is the respective ExoPlayer release for each Media3 release? Does the current Media3 1.0.0-alpha01 matches ExoPlayer 2.15.1 (for API compatibility when migrating, applying patches etc.)? I think this could be useful information in release notes.

currentMediaItem is empty when a CastPlayer is being used

I'm using a MediaLibraryService that switches the player from ExoPlayer to CastPlayer when a CastSession is available. When the ExoPlayer is active, calling getCurrentMediaItem() from a MediaController returns the correct MediaItem, but if the same call is done when the CastPlayer is the active one, I receive an empty MediaItem.

The method getWindow(...) inside CastTimeline is the one creating and using an empty MediaItem instead of the expected currentMediaItem.

IllegalStateException in MediaController.notifyAccepted()

Hi. I noticed the following crash report in Crashlytics for my app.

Example stack trace (copied from Firebase Crashlytics)

Fatal Exception: java.lang.IllegalStateException
       at androidx.media3.common.util.Assertions.checkState(Assertions.java:85)
       at androidx.media3.session.MediaController.notifyAccepted(MediaController.java:1800)
       at androidx.media3.session.MediaControllerImplLegacy.updateControllerInfo(MediaControllerImplLegacy.java:1357)
       at androidx.media3.session.MediaControllerImplLegacy.handleNewLegacyParameters(MediaControllerImplLegacy.java:1318)
       at androidx.media3.session.MediaControllerImplLegacy.access$500(MediaControllerImplLegacy.java:109)
       at androidx.media3.session.MediaControllerImplLegacy$ControllerCompatCallback.onSessionReady(MediaControllerImplLegacy.java:1559)
       at android.support.v4.media.session.MediaControllerCompat$Callback$MessageHandler.handleMessage(MediaControllerCompat.java:1121)
       at android.os.Handler.dispatchMessage(Handler.java:107)
       at android.os.Looper.loop(Looper.java:214)
       at android.app.ActivityThread.main(ActivityThread.java:7386)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:980)

Where the exception thrown

Library versions

  • androidx.media3: Built from source (Commit ID: f92ae23)
  • androidx.media: v1.5.0

Affected Android versions (obtained info from Firebase Crashlytics)

  • Android 8, 9, 10, 11 and 12
    (older OS might also be affected)

How to reproduce the issue?

Sorry, I have not succeeded in reproducing the issue yet. I'll post details if I can get any info.

onMediaMetadataChanged not called for media controller

After getting a handle on the MediaController from the MediaService and attaching a PlayerListener to it, all callback methods are called except onMediaMetadataChanged and onMetadata.

These callbacks are called as expected on the Exoplayer instance created inside the MediaService class along with all other callback methods. I am testing with a live online stream url.

From my Activity class I have this code where the metadata related callbacks are not getting called.

val mediaControllerFuture = MediaController.Builder(
    this,
    SessionToken(this, ComponentName(this, MediaPlayerService::class.java))
)
    .buildAsync()

mediaControllerFuture.addListener({
    val controller = mediaControllerFuture.get()
    controller.addListener(object : Player.Listener {
        override fun onMetadata(metadata: Metadata) {
            super.onMetadata(metadata)
        }

        override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) {
            super.onMediaMetadataChanged(mediaMetadata)
        }
    })

}, Executors.newSingleThreadExecutor())

And this is how I initialise the player and session in the MediaService class. All the callbacks that are attached directly to the exoplayer instance are getting called with appropriate expected values.

private fun initializePlayerAndSession() {
    player = ExoPlayer
        .Builder(this)
        .setAudioAttributes(
            AudioAttributes.Builder()
                .setUsage(C.USAGE_MEDIA)
                .setContentType(C.CONTENT_TYPE_MUSIC)
                .build(),
            true
        )
        .setHandleAudioBecomingNoisy(true)
        .setWakeMode(WAKE_MODE_NETWORK)
        .build()

    player.addListener(object : Player.Listener {
        override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) {

        }

        override fun onMetadata(metadata: Metadata) {
            super.onMetadata(metadata)
        }

        override fun onIsPlayingChanged(isPlaying: Boolean) {
            super.onIsPlayingChanged(isPlaying)
        }
    })

val mediaSessionBuilder = MediaSession
            .Builder(this, player)
            .setSessionCallback(object : MediaSession.SessionCallback {
                override fun onConnect(
                    session: MediaSession,
                    controller: MediaSession.ControllerInfo
                ): MediaSession.ConnectionResult {
                    return MediaSession.ConnectionResult.accept(sessionCommands, playerCommands)
                }
            })
            .setSessionActivity(intent)


        val setItemFiller = MediaSession.Builder::class.java.getDeclaredMethod(
            "setMediaItemFiller",
            MediaSession.MediaItemFiller::class.java
        )

        setItemFiller.isAccessible = true

        setItemFiller.invoke(
            mediaSessionBuilder,
            CustomMediaItemFiller()
        )

        mediaSession = mediaSessionBuilder.build()
}
  • AndroidX Media version number - 1.1.0-alpha01
  • Android version - 10
  • Android device - Samsung Galaxy Note 9

Feature request: disable automatic foreground service management

Use case description

We would like to disable the automatic foreground service management of the MediaSessionService or at least get a callback wenn the foreground service is stopped to be able to keep the service alive for a longer time. Unfortunately the Service.stopForeground method is final and we can't override it.

Proposed solution

Provide an option whether handling the foreground service should be done by the MediaSessionService or whether it's handled by the implementation of that service itself.

Alternatives considered

Provide a callback or a startForeground/stopForeground method that can be overridden.

An exception might be thrown inside MediaControllerImplBase.onPlayerInfoChanged()

Hi. I found the following crash report on Crashlytics. An exception thrown inside of the MediaControllerImplBase.onPlayerInfoChanged().

(Click here to display the full stacktrace)
Fatal Exception: java.lang.IndexOutOfBoundsException: index (*) must be less than size (*)

Example 1

Fatal Exception: java.lang.IndexOutOfBoundsException: index (4) must be less than size (4)
       at com.google.common.base.Preconditions.checkElementIndex(Preconditions.java:1355)
       at com.google.common.base.Preconditions.checkElementIndex(Preconditions.java:1337)
       at com.google.common.collect.RegularImmutableList.get(RegularImmutableList.java:82)
       at androidx.media3.common.Timeline$RemotableTimeline.getWindow(Timeline.java:1530)
       at androidx.media3.common.Timeline.getWindow(Timeline.java:1118)
       at androidx.media3.session.PlayerInfo.getCurrentMediaItem(PlayerInfo.java:637)
       at androidx.media3.session.MediaControllerImplBase.onPlayerInfoChanged(MediaControllerImplBase.java:2383)
       at androidx.media3.session.MediaControllerStub.lambda$onPlayerInfoChanged$9(MediaControllerStub.java:180)
       at androidx.media3.session.MediaControllerStub.$r8$lambda$X59hShI5lo8-62FDwGBWO2tdQfo(MediaControllerStub.java)
       at androidx.media3.session.MediaControllerStub$$InternalSyntheticLambda$2$4a3a6820449623629e404f4bef3c0240e0815b9ab568a3a89e0663d3f4d5622f$0.run(MediaControllerStub.java:4)
       at androidx.media3.session.MediaControllerStub.lambda$dispatchControllerTaskOnHandler$12(MediaControllerStub.java:271)
       at androidx.media3.session.MediaControllerStub.$r8$lambda$YGpG-xpiKgSpdH847m1pEUdLemY(MediaControllerStub.java)
       at androidx.media3.session.MediaControllerStub$$InternalSyntheticLambda$3$c38f624292a1f1ed4af00152f20c76bcf37ccebfce0326ec339c89e37f90a1b6$0.run(MediaControllerStub.java:4)
       at androidx.media3.common.util.Util.postOrRun(Util.java:528)
       at androidx.media3.session.MediaControllerStub.dispatchControllerTaskOnHandler(MediaControllerStub.java:263)
       at androidx.media3.session.MediaControllerStub.onPlayerInfoChanged(MediaControllerStub.java:178)
       at androidx.media3.session.MediaSessionStub$Controller2Cb.onPlayerInfoChanged(MediaSessionStub.java:1734)
       at androidx.media3.session.MediaSessionImpl.dispatchOnPlayerInfoChanged(MediaSessionImpl.java:393)
       at androidx.media3.session.MediaSessionImpl.access$600(MediaSessionImpl.java:80)
       at androidx.media3.session.MediaSessionImpl$PlayerInfoChangedHandler.handleMessage(MediaSessionImpl.java:1186)
       at android.os.Handler.dispatchMessage(Handler.java:106)
       at android.os.Looper.loop(Looper.java:246)
       at android.app.ActivityThread.main(ActivityThread.java:8653)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)

Example 2

Fatal Exception: java.lang.IndexOutOfBoundsException: index (13) must be less than size (1)
       at com.google.common.base.Preconditions.checkElementIndex(Preconditions.java:1355)
       at com.google.common.base.Preconditions.checkElementIndex(Preconditions.java:1337)
       at com.google.common.collect.RegularImmutableList.get(RegularImmutableList.java:82)
       at androidx.media3.common.Timeline$RemotableTimeline.getWindow(Timeline.java:1530)
       at androidx.media3.common.Timeline.getWindow(Timeline.java:1118)
       at androidx.media3.session.PlayerInfo.getCurrentMediaItem(PlayerInfo.java:637)
       at androidx.media3.session.MediaControllerImplBase.onPlayerInfoChanged(MediaControllerImplBase.java:2383)
       at androidx.media3.session.MediaControllerStub.lambda$onPlayerInfoChanged$9(MediaControllerStub.java:180)
       at androidx.media3.session.MediaControllerStub.$r8$lambda$X59hShI5lo8-62FDwGBWO2tdQfo(MediaControllerStub.java)
       at androidx.media3.session.MediaControllerStub$$InternalSyntheticLambda$2$4a3a6820449623629e404f4bef3c0240e0815b9ab568a3a89e0663d3f4d5622f$0.run(MediaControllerStub.java:4)
       at androidx.media3.session.MediaControllerStub.lambda$dispatchControllerTaskOnHandler$12(MediaControllerStub.java:271)
       at androidx.media3.session.MediaControllerStub.$r8$lambda$YGpG-xpiKgSpdH847m1pEUdLemY(MediaControllerStub.java)
       at androidx.media3.session.MediaControllerStub$$InternalSyntheticLambda$3$c38f624292a1f1ed4af00152f20c76bcf37ccebfce0326ec339c89e37f90a1b6$0.run(MediaControllerStub.java:4)
       at androidx.media3.common.util.Util.postOrRun(Util.java:528)
       at androidx.media3.session.MediaControllerStub.dispatchControllerTaskOnHandler(MediaControllerStub.java:263)
       at androidx.media3.session.MediaControllerStub.onPlayerInfoChanged(MediaControllerStub.java:178)
       at androidx.media3.session.MediaSessionStub$Controller2Cb.onPlayerInfoChanged(MediaSessionStub.java:1734)
       at androidx.media3.session.MediaSessionImpl.dispatchOnPlayerInfoChanged(MediaSessionImpl.java:393)
       at androidx.media3.session.MediaSessionImpl.access$600(MediaSessionImpl.java:80)
       at androidx.media3.session.MediaSessionImpl$PlayerInfoChangedHandler.handleMessage(MediaSessionImpl.java:1186)
       at android.os.Handler.dispatchMessage(Handler.java:106)
       at android.os.Looper.loop(Looper.java:193)
       at android.app.ActivityThread.main(ActivityThread.java:6819)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:497)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:912)

Library versions

  • androidx.media3: Built from source (Commit ID: f92ae23)

Affected devices

  • Samsung Galaxy S22 Ultra (Android 12)
  • Samsung Galaxy A71 (Android 11)
  • Infinix Smart 4 (Android 9)
  • and many more...

How to reproduce the issue?

Sorry, I have not succeeded in reproducing the issue yet. I'll post details if I can get any info.

How to handle an indirect media uri

SoundCloud API https://developers.soundcloud.com/docs/api/guide#playing returns a JSON document with temporary streams via a CDN

{
  "url": "https://cf-hls-media.sndcdn.com/playlist/xxx.128.mp3/playlist.m3u8?Policy=..."
}

Is there a pattern for how to use the indirect URL MediaItem.fromUri(indirectUrl) and have the DataSource.Factory follow the redirect from the response body? Or would it be best to implement a HTTP Interceptor to rewrite the response as a 307 Temporary Redirect?

How to download + transform

I'm using Media3 on Wear, and want to make sure I'm always sending AAC over Bluetooth. This seems very easy to convert using the

https://github.com/androidx/media/blob/e6242690ffbd661fcf1fdc5e063d11dda3dfbd61/libraries/transformer/src/main/java/androidx/media3/transformer/TranscodingTransformer.java

But as it outputs to a new path, I wasn't sure how best to transcode items that I have just downloaded for offline, or even whether I can transcode as I'm downloading so I only have the preferred codec file, and don't have to replace the media item with a different local URL to replace the external HTTPS URL.

Cancels notificationManager provided by the service

There is an updateNotification callback in both alpha01 and Media2, and I just need to return null to use my own defined notificationManager and NotificationListener. Now alpha02 this method have no, followed by a setMediaNotificationProvider, but I will not use.

Now there will also be a UI refresh problem for the notification provided, click the problem. For example, after clicking pause, the pause button will return to the pause button after playing, and clicking on it will not work. The progress bar often fails to refresh.How do I stop using this Notification and use my custom Notification Manager.
https://github.com/huaweikai/testDemo/blob/d2599896e3346e1d309f2a9e5ae0f4d2f539a019/DA4D55E705C3EC32BE0C49410B0803D6.mp4

App crashes if skip notification action is used while paused

Steps to reproduce

  1. Launch app and play audio using a MediaLibraryService/MediaSessionService
  2. Remove app from recents while playing
  3. Pause media from notification
  4. Press skip to next from notification
  5. Wait

The app will crash with:

android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()

Cause

MediaNotificationHandler method createPendingIntent() is used for the creation of its NotificationCompat.Actions. This method decides to call getService or getForegroundService and it correctly checks for PAUSE and STOP actions that won't put the service in the foreground, but there are more actions that should be considered. ACTION_SKIP_TO_PREVIOUS, ACTION_SKIP_TO_NEXT and many more actions won't put the service in the foreground and all of them will crash the app if getForegroundService is used.

Also, I think that media2 has the same createPendingIntent and probably has the same problem.

[Question] Why test-session-common and test-session-common projects are not listed in the settings.gradle

We need to add the following lines of code to the settings.gradle to run tests related to media3-session for now. Is this intended or not?

include modulePrefix + 'test-session-common'
project(modulePrefix + 'test-session-common').projectDir = new File(rootDir, 'libraries/test_session_common')

include modulePrefix + 'test-session-current'
project(modulePrefix + 'test-session-current').projectDir = new File(rootDir, 'libraries/test_session_current')

Support for custom actions

In androidx.media we could add custom actions (action + name + icon) to the PlaybackStateCompat, and the actions added were displayed in UIs like Android Auto.

I've seen references to custom SessionCommands, but PlayerWrapper.createPlaybackStateCompat doesn't use them to create the PlaybackStateCompat, so I assume they serve a different purpose.

Are custom actions supported in media3?

Volume Normalisation with Audio Offload enabled

For a music/podcast app - if the app requires volume normalisation, this can't be implemented in an AudioProcessor when Audio Offload is enabled.

But can the volume multiplier be used instead? Are there other ways to achieve this?

How can I get

Unfortunately we can't answer all questions. Unclear questions or questions with
insufficient information may not get attention.

Before filing a question:

When filing a question:

Describe your question in detail.

In case your question refers to a problem you are seeing in your app:

  • Output of running $ adb bugreport in the console

In case your question is related to a piece of media:

  • URI to test content
  • For protected content:
    • DRM scheme and license server URL
    • Authentication HTTP headers

Don't forget to check ExoPlayer's supported formats and devices, if applicable
(https://exoplayer.dev/supported-formats.html).

If there's something you don't want to post publicly, please submit the issue,
then email the link/bug report to [email protected] using a subject in the
format "Issue #1234", where #1234 is your issue number (we don't reply to
emails).

MediaCodecAudioRenderer: Audio codec error (API 31)

Hello there 👋
I ran into this audio codec error by seeking the media item rapidly, seems the codec can't be started after flushing, and it's only happened on API 31 for me.

2021-12-21 17:32:27.065 10626-11013/com.bandlab.bandlab.test E/MediaCodecAudioRenderer: Audio codec error
      java.lang.IllegalStateException: start failed
        at android.media.MediaCodec.native_start(Native Method)
        at android.media.MediaCodec.start(MediaCodec.java:2284)
        at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecAdapter.$r8$lambda$eDpY_fTfY9aVkUSlR57RZPPXgr0(Unknown Source:0)
        at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecAdapter$$ExternalSyntheticLambda1.run(Unknown Source:2)
        at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecCallback.onFlushCompletedSynchronized(AsynchronousMediaCodecCallback.java:267)
        at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecCallback.onFlushCompleted(AsynchronousMediaCodecCallback.java:246)
        at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecCallback.lambda$flushAsync$0$androidx-media3-exoplayer-mediacodec-AsynchronousMediaCodecCallback(AsynchronousMediaCodecCallback.java:203)
        at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecCallback$$ExternalSyntheticLambda0.run(Unknown Source:4)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loopOnce(Looper.java:201)
        at android.os.Looper.loop(Looper.java:288)
        at android.os.HandlerThread.run(HandlerThread.java:67)
2021-12-21 17:32:27.068 10626-11013/com.bandlab.bandlab.test E/ExoPlayerImplInternal: Playback error
      androidx.media3.exoplayer.ExoPlaybackException: MediaCodecAudioRenderer error, index=1, format=Format(1, null, null, audio/mp4a-latm, mp4a.40.2, -1, und, [-1, -1, -1.0], [2, 44100]), format_supported=YES
        at androidx.media3.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:570)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loopOnce(Looper.java:201)
        at android.os.Looper.loop(Looper.java:288)
        at android.os.HandlerThread.run(HandlerThread.java:67)
     Caused by: androidx.media3.exoplayer.mediacodec.MediaCodecDecoderException: Decoder failed: c2.android.aac.decoder
        at androidx.media3.exoplayer.mediacodec.MediaCodecRenderer.createDecoderException(MediaCodecRenderer.java:920)
        at androidx.media3.exoplayer.mediacodec.MediaCodecRenderer.render(MediaCodecRenderer.java:801)
        at androidx.media3.exoplayer.ExoPlayerImplInternal.doSomeWork(ExoPlayerImplInternal.java:990)
        at androidx.media3.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:494)
        at android.os.Handler.dispatchMessage(Handler.java:102) 
        at android.os.Looper.loopOnce(Looper.java:201) 
        at android.os.Looper.loop(Looper.java:288) 
        at android.os.HandlerThread.run(HandlerThread.java:67) 
     Caused by: java.lang.IllegalStateException: start failed
        at android.media.MediaCodec.native_start(Native Method)
        at android.media.MediaCodec.start(MediaCodec.java:2284)
        at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecAdapter.$r8$lambda$eDpY_fTfY9aVkUSlR57RZPPXgr0(Unknown Source:0)
        at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecAdapter$$ExternalSyntheticLambda1.run(Unknown Source:2)
        at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecCallback.onFlushCompletedSynchronized(AsynchronousMediaCodecCallback.java:267)
        at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecCallback.onFlushCompleted(AsynchronousMediaCodecCallback.java:246)
        at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecCallback.lambda$flushAsync$0$androidx-media3-exoplayer-mediacodec-AsynchronousMediaCodecCallback(AsynchronousMediaCodecCallback.java:203)
        at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecCallback$$ExternalSyntheticLambda0.run(Unknown Source:4)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loopOnce(Looper.java:201) 
        at android.os.Looper.loop(Looper.java:288) 
        at android.os.HandlerThread.run(HandlerThread.java:67) 

I've found the exact same error in ExoPlayer's repo, and the reporter said it was fixed in 2.16.1, may I know if it's a known issue and what's the current ExoPlayer version that media3 is using? Thanks!
google/ExoPlayer#9716

DefaultNotificationProvider picks unreasonable icon

The DefaultNotificationProvider uses the application launcher icon (context.getApplicationInfo().icon)

Modern Notifications only take the alpha channel of the supplied icon. Unless the ic_launcher icon has transparency th notification will only be white.

Suggestion: default to using ic_notification..

Music Notification Artwork Not Showing

image
I set the artworkUrl, but it doesn't show anything. Did I set it wrong?

My MediaItemBuilder:

               addMediaItem(
                            MediaItem.Builder()
                                .setMediaId(track.id.toString())
                                .setMediaMetadata(
                                    MediaMetadata.Builder()
                                        .setTitle(track.name)
                                        .setArtist(track.ar.joinToString(", ") { ar -> ar.name })
                                        .setMediaUri(Uri.parse("$RainMusicProtocol://music?id=${track.id}"))
                                        .setArtworkUri(Uri.parse(track.al.picUrl))
                                        .build()
                                )
                                .build()
               )

How to update current media metadata (user rating) without interrupting the playback?

I am using androidx.media3 to develop an audio player app which provides users with an option to rate currently playng media. So, inside my MediaSessionCallback I do this:

override fun onSetRating(
    session: MediaSession,
    controller: ControllerInfo,
    rating: Rating
): ListenableFuture<SessionResult> {

    val item = session.player.currentMediaItem

    item?.let {
        val metadata  = it.mediaMetadata.buildUpon().setUserRating(rating).build()
        val mediaItem = it.buildUpon().setMediaMetadata(metadata).build()
        session.player.setMediaItem(mediaItem, false)
    }
    
    return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
}

This works, but with the only caveat - it interrupts the audio for the moment. Please, help me to understand how user rating should work.

Occasional crashes on different devices caused by checkStateNotNull in MediaSessionService

Unfortunately I haven't been able to reproduce the issue on a device here. I observe it in alpha02 on roughly 5% of the sessions. Android versions range from 9 to 12 and the devices are different Samsung Galaxy units.

It is caused exactly here:

actionFactory = checkStateNotNull(this.actionFactory);

Trace:

java.lang.RuntimeException: 
  at android.app.ActivityThread.handleServiceArgs (ActivityThread.java:5112)
  at android.app.ActivityThread.access$2200 (ActivityThread.java:310)
  at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2321)
  at android.os.Handler.dispatchMessage (Handler.java:106)
  at android.os.Looper.loopOnce (Looper.java:226)
  at android.os.Looper.loop (Looper.java:313)
  at android.app.ActivityThread.main (ActivityThread.java:8641)
  at java.lang.reflect.Method.invoke (Native Method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:567)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1133)
Caused by: java.lang.IllegalStateException: 
  at androidx.media3.common.util.Assertions.checkStateNotNull (Assertions.java:117)
  at androidx.media3.session.MediaSessionService.onStartCommand (MediaSessionService.java:326)
  at android.app.ActivityThread.handleServiceArgs (ActivityThread.java:5094)`

This is how I create the session (not sure it's relevant):

@androidx.annotation.OptIn(UnstableApi::class)
private fun wireMediaSession() {
    Timber.d("PlayerService | wireMediaSession")
    val renderersFactory = DefaultRenderersFactory(this)
        // TODO: Figure out why this causes playback not to work on some devices
        //.setEnableAudioOffload(true)
    val audioAttributes = AudioAttributes.Builder()
        .setUsage(USAGE_MEDIA)
        .setContentType(CONTENT_TYPE_SPEECH)
        .build()
    exoPlayer = ExoPlayer.Builder(this, renderersFactory)
        .setWakeMode(WAKE_MODE_NETWORK)
        .setAudioAttributes(audioAttributes, true)
        .build()
    val parentScreenIntent = Intent(this, MainActivity::class.java)
    val sessionActivityPendingIntent =
        TaskStackBuilder.create(this).run {
            addNextIntent(parentScreenIntent)
            val immutableFlag = if (Build.VERSION.SDK_INT >= 23) PendingIntent.FLAG_IMMUTABLE else 0
            getPendingIntent(0, immutableFlag or PendingIntent.FLAG_UPDATE_CURRENT)
        } ?: return
    mediaSession = MediaLibrarySession.Builder(this, exoPlayer, librarySessionCallback)
        .setMediaItemFiller(mediaItemFiller)
        .setSessionActivity(sessionActivityPendingIntent)
        .build()
}

Thanks for a nice library :)

Ongoing Media Notification for Wear not working with certain media

Test is here testPlayCausesNotification.
yschimke@637b487

The test doesn't fail, the notification check succeeds, but the two media items in the test behave differently.

Podcast MP3 - Shows the ongoing notification and (with a real app not a test) the Media title in the recent app switcher.

    val milkJawn = Channel(
            "a",
            "Milk Jawn's ice cream pop-up to become a brick-and-mortar",
            "What's Cooking",
            "https://traffic.omny.fm/d/clips/4b5f9d6d-9214-48cb-8455-a73200038129/a76af74b-4211-43f1-8fa6-a78e00b5ae4b/acffa77d-5b31-499e-b38b-adf800e1778b/audio.mp3",
            "https://cdn.player.fm/images/14416069/series/stoRUvKcFOzInZ1X/512.jpg",
    )

image

Radio Stream - Doesn't show notification (or media title in app switcher)

    val BBCRadio1 = Channel(
            "1",
            "Radio 1",
            "BBC",
            "https://a.files.bbci.co.uk/media/live/manifesto/audio/simulcast/hls/nonuk/sbr_vlow/ak/bbc_radio_one.m3u8",
            "https://sounds.files.bbci.co.uk/2.3.0/networks/bbc_radio_one/colour_default.svg",
    )

image

Can reproduce on Devices (Samsung GW4, Suunto7) and Emulator (API 30).

Androidx Media3 - 1.0.0-alpha01
API 30 Emulator

Support media resumption with session demo app

To support the "Media Resumption" feature introduced in Android 11, we need to "implement a MediaSession callback for onPlay()".

With media3, the MediaLibrarySessionCallback is replacing onPrepareFromMediaId, onPrepareFromSearch, onPrepareFromUri, onPlayFromMediaId, onPlayFromSearch, onPlayFromUri with onSetMediaUri, but the simple onPlay with no input is not included and I can't find an easy way to implement it.

Edit: Is not included in the documentation, but the "Media Resumption" feature is also calling onPrepare() and, after reading through MediaSessionLegacyStub, I was able to "fake" an onPrepare() by catching COMMAND_PREPARE (only dispatched inside onPrepare with no input). I couldn't do the same to fake the onPlay since it uses the COMMAND_PLAY_PAUSE and that command could mean onPlay or onPause, but the onPrepare replacement seems to be enough to support the feature for now.

Media3 audio playback on Wear Emulator is too fast

Similar to google/ExoPlayer#7903

The Wear emulator (API 30) plays back media at roughly 2x the normal speed.

I'm still debugging, and trying to work out if it's the same on different emulators, or whether I can disable HardwareDecoder in the emulator ini file, but raising here in case it's a known issue.

I'll update this issue with a reproducible test case ASAP.

Media3 ExoPlayer stops playback of audio after phone sleeps

Extending MediaLibraryService and setting the wake mode - C.WAKE_MODE_LOCAL, playback stops after the phone goes to sleep.

Player class;

@SuppressLint("UnsafeOptInUsageError")
class MusicPlayerService @Inject constructor(): MediaLibraryService() {

    private lateinit var player: ExoPlayer
    private lateinit var mediaLibrarySession: MediaLibrarySession
    private val librarySessionCallback = CustomMediaLibrarySessionCallback()

    companion object {
        val TAG: String = MusicPlayerService::class.java.simpleName
        const val PLAY = 0
        const val PLAY_NEXT = 1
        const val ADD_TO_QUEUE = 2
    }

    override fun onCreate() {
        super.onCreate()
        initializeSessionAndPlayer()
    }

    override fun onDestroy() {
        player.release()
        mediaLibrarySession.release()
        super.onDestroy()
    }

    override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession {
        return mediaLibrarySession
    }

    // https://www.youtube.com/watch?v=sTIBDcyCmCg
    private fun initializeSessionAndPlayer() {
        player = Builder(this)
            .setAudioAttributes(AudioAttributes.DEFAULT, true)
            .setHandleAudioBecomingNoisy(true)
            .setWakeMode(C.WAKE_MODE_LOCAL)
            .build()

        mediaLibrarySession =
            MediaLibrarySession.Builder(this, player, librarySessionCallback)
                .setMediaItemFiller(CustomMediaItemFiller())
                .setId("1")
                .build()
    }

    private inner class CustomMediaLibrarySessionCallback :
        MediaLibrarySession.MediaLibrarySessionCallback

    inner class CustomMediaItemFiller : MediaSession.MediaItemFiller {
        override fun fillInLocalConfiguration(
            session: MediaSession,
            controller: MediaSession.ControllerInfo,
            mediaItem: MediaItem
        ): MediaItem {
            // Return the media item that it will be played
            return mediaItem.buildUpon()
                .setMediaId(mediaItem.mediaId)
                .setUri(mediaItem.mediaMetadata.mediaUri)
                .setMediaMetadata(mediaItem.mediaMetadata)
                .build()
        }
    }
}

initialised from a fragment;


@AndroidEntryPoint
class ExoplayerFragment @Inject constructor() :
    BaseAudioFragment(),
    View.OnClickListener {

   override fun onStart() {
        initializeController()
        super.onStart()
    }

    override fun onStop() {
        super.onStop()
        releaseController()
    }

    override fun onDestroyView() {
        super.onDestroyView()
        mDisposable.dispose()
        bottomSheetFragment = null
    }

    override fun onDestroy() {
        super.onDestroy()
        if (!mDisposable.isDisposed) {
            mDisposable.dispose()
        }
    }

    @androidx.annotation.OptIn(UnstableApi::class)
    private fun initializeController() {
        val sessionToken = SessionToken(
            requireContext(),
            ComponentName(requireContext(), MusicPlayerService::class.java)
        )
        controllerFuture =
            MediaController.Builder(
                requireContext(),
                sessionToken
            ).buildAsync()

        controllerFuture.addListener({ setController() }, MoreExecutors.directExecutor())
    }

    private fun releaseController() {
        MediaController.releaseFuture(controllerFuture)
    }
 ...
}

In android manifest included both;

<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

Make DefaultMediaNotificationProvider more customizable

I want to customize the smallIcon as well as the setShowActionsInCompactView().
Unfortunately I can't extend the DefaultMediaNotificationProvider because it is marked private in the package.

So the only solution I found was to copy the code from DefaultMediaNotificationProvider.java to my project and then change it there.

But that is not very nice, and if upstream it is changed, we won't get the changes.

Correct behaviour when switching `Player` in `MediaSession`

As per #25 i've implemented a custom Player which caches the MediaItem(s) internally and switches from local playback using an ExoPlayer instance to a CastPlayer instance when a cast session is available and vice versa. This composite Player is passed to the MediaSession and no subsequent calls to MediaSession#setPlayer are made.

player = CompositePlayer(localPlayer = localPlayer,castPlayer = castPlayer)
mediaSession = MediaSession.Builder(this, player)

The way I am currently switching players internally is: stop the playback of the current player using player#stop() and clear the playlist in the current player using player#clearMediaItems(). Afterwards set the items to the new player using player#setMediaItem(), player#prepare() and player#play().

This all works fine, except when switching from local playback to cast playback a new cast notification is created, yet the "old" media notification is also still present (however it shows the device name of the device I am casting to, instead of "phone"), so theres 2 notifications at the same time, also the "old" notification is not updated anymore (presumably because it is connected to the old local playback which is not used anymore?).

My question: what is supposed to be the correct UX? Is there something wrong with my implementation? I feel like there should only be 1 notification: a cast notification when casting and 1 notification when using local playback. Also since I am using a PlayerControlView in the UI this is only connected to one player, either local playback or cast playback depending on what's active at the moment.

I am running a Pixel 4a with Android 12.

Old extensions & Media3 ExoPlayer compatibility

We rely on some ExoPlayer extensions for audio playback within our app. These include ffmpeg, mpegh, flac & some other proprietary extensions. They have been developed & built based on com.google.android.exoplayer2. And as we suspect, these proprietary extensions will require some (unknown :)) time to be updated to use media3.

So far we decided to continue to use old com.google.android.ExoPlayer that works fine with our extensions and plug that into Media3 MediaSession by creating an adapter. That adapter would implement androidx.media3.common.Player, proxying calls to a wrapped com.google.android.ExoPlayer. However, this is going to be really cumbersome due to lots of conversions of objects.

Is there any chance that some compatibility tool will be provided or should we go with the described adapter? Or maybe there's even a better & recommended way to do this?

DaiStreamRequest class not found

Steps to reproduce:

  1. Clone the repo
  2. Build the project / Run demo app

File Name : ImaServerSideDaiMediaSourceFactory
Line 737: Not able to import class DaiStreamRequest

There are several other errors that prevent running the demo app.

Why is the localConfiguration of a MediaItem removed when set on a mediaController?

Now I am using the media3 do a music software. I have a function to save the last playlist, so the next time I launch the app, I load the last playlist and index directly. But I found that in Media3 mediaLibrarySession must set the MediaItemFiller, leaving it blank will also cause the check to be empty.

The official demo reads like this:

    inner class CustomMediaItemFiller : MediaSession.MediaItemFiller {
        override fun fillInLocalConfiguration(
            session: MediaSession,
            controller: MediaSession.ControllerInfo,
            mediaItem: MediaItem
        ): MediaItem {
            return itemTree.getItem(mediaItem.mediaId) ?: mediaItem
        }
    }

So when I start up my app, I go to the database and retrieve the list and I go to setMediaItems, and it jumps to the MediaItemFiller, which looks from the ItemTree first, but some of my songs are going to be in the album, and they're not going to scan for them when they first load. But the mediaItem that was returned is the mediaItem that was passed in, and I see that its Uri is already empty, which will cause the localConfiguration to still be empty when I check.

    inner class CustomMediaItemFiller : MediaSession.MediaItemFiller {
        override fun fillInLocalConfiguration(
            session: MediaSession,
            controller: MediaSession.ControllerInfo,
            mediaItem: MediaItem
        ): MediaItem {
            return MediaItem.Builder()
                .setMediaMetadata(mediaItem.mediaMetadata)
                // I need to reset the Uri,but why?
                .setUri(mediaItem.mediaMetadata.mediaUri)
                .setMediaId(mediaItem.mediaId)
                .build()
        }
    }

ForegroundServiceStartNotAllowedException when playing from a home screen widget

Due to the behavior changes of Android 12, the app crashes when I play a track then the app loses AudioFocus, for which I have a callback when the app gains audio focus AudioManager.AUDIOFOCUS_GAIN that's where the media player crashes
The MediaNotificationHandler.java#L129 throws a ForegroundServiceStartNotAllowedException.
I assume when the app loses audio focus stopForegroundServiceIfNeeded(); is called

Would we need to update the playing media to WorkManager or what is the official guide to support playing media while the app is in the background on Android 12?

  • AndroidX Media 3
  • Android version 12
  • Android device Pixel 5

How to gate playback on Bluetooth Headset connection

I'd like to ensure playback is only active when a bluetooth headset is connected.

For stopping when the audio output changes it seems like .setHandleAudioBecomingNoisy(true) is the right approach.

Is LoadControl, the right API when the user clicks play to trigger a headset selection before continuing?

I've seen the references in google/ExoPlayer#9694, so I understand I'll need to implement this myself, but am curious which APIs are the right ones to plug into, to effect availableCommands for example? Or maybe allow the command, but launch the Bluetooth settings automatically and wait for a connection before playing?

How to play video media on child process

I have a player service on a child process named ":player", and the UI components is in main process.
I want to know how to render video from child process to VideoView in main process.

Users report my app isn't working on Samsung Galaxy S21+

I've just built and released an application with media3.

Links:

Versions:

  • media3: 1.0.0-alpha01

Everything seems to work fine for most devices, but we've received 2 reports from users of Samsung Galaxy S21+ devices who are unable to play any of the media.

I haven't been able to verify the issue as I don't have a device at hand.

Another thing: enableAudioOffload causes things not to work properly on Pixel 6 Pro and perhaps more devices: https://github.com/denuafhaengige/duah-android/blob/master/app/src/player/Service.kt#L106

Hope for help/guidance, thanks.

Adding MediaItems from MediaController immediately shows unresponsive media notification

On app startup, I am adding a couple of MediaItems to a MediaController which is connected to the MediaSessionService. This immediately shows an media notification which is unresponsive (play/pause does nothing except updating the notification buttons but no playback). After starting playback from the PlayerControlView in the app, the notification starts to work.

My question: is this expected behaviour or is there something wrong with my configuration? I would expect the media notification to show up as soon as playback starts for the first time, not when the app starts. And if the media notification is supposed to show immediately after adding items and connecting the controls I would expect the media notification to be able the successfully start the playback.

Relevant code:

The MediaSessionService implementation:

class StreamService : MediaSessionService() {

    private val getAllStreams: GetAllStreams by inject()

    private val serviceJob = SupervisorJob()
    private val serviceScope = CoroutineScope(Dispatchers.Main + serviceJob)

    private lateinit var urlsMap: Map<String, String>
    private lateinit var player: ExoPlayer
    private lateinit var mediaSession: MediaSession

    override fun onCreate() {
        super.onCreate()
        serviceScope.launch {
            val streams = getAllStreams()
            urlsMap = streams.map { it.id to it.url }.toMap() // cache urls
            initialize()
        }
    }

    override fun onDestroy() {
        player.release()
        mediaSession.release()
        super.onDestroy()
    }

    /**
     * Connect [MediaSession] to this [MediaSessionService]
     */
    override fun onGetSession(controllerInfo: MediaSession.ControllerInfo) = mediaSession

    private fun initialize() {
        player = ExoPlayer.Builder(applicationContext)
            .setAudioAttributes(
                AudioAttributes.Builder()
                    .setUsage(C.USAGE_MEDIA)
                    .setContentType(C.CONTENT_TYPE_MUSIC)
                    .build(), true // Automatic requesting and dropping audio focus
            )
            .setHandleAudioBecomingNoisy(true) // Handle headphones disconnect
            .setWakeMode(C.WAKE_MODE_NETWORK) // Wake+WiFi lock while playing
            .build()

        val intent = Intent(this, MainActivity::class.java)
        val immutableFlag = if (Build.VERSION.SDK_INT >= 23) FLAG_IMMUTABLE else 0
        val pendingIntent = PendingIntent.getActivity(
            this,
            0,
            intent,
            immutableFlag or FLAG_UPDATE_CURRENT
        )

        val builder = MediaSession.Builder(applicationContext, player)
            .setSessionActivity(pendingIntent)

        // Using reflection since relevant fix has not been released
        // https://github.com/androidx/media/commit/2a62a5ee302d694eb9c7099024d13607f6143830
        builder.callPrivateFunc("setMediaItemFiller", CustomMediaItemFiller(streamUrls = urlsMap))
        mediaSession = builder.build()
    }

    class CustomMediaItemFiller(private val streamUrls: Map<String, String>) :
        MediaSession.MediaItemFiller {
        override fun fillInLocalConfiguration(
            session: MediaSession,
            controller: MediaSession.ControllerInfo,
            mediaItem: MediaItem
        ): MediaItem = mediaItem
            .buildUpon()
            .setUri(Uri.parse(streamUrls[mediaItem.mediaId]))
            .build()
    }
}

The MediaController implementation:

class StreamManagerImpl(
    private val application: Application,
) : StreamManager {

    private lateinit var controllerFuture: ListenableFuture<MediaController>

    private val controller: MediaController?
        get() = if (controllerFuture.isDone) controllerFuture.get() else null

    override fun initialize(streams: List<Stream>, controls: StyledPlayerControls) {
        controllerFuture = MediaController.Builder(
            application,
            SessionToken(application, ComponentName(application, StreamService::class.java))
        ).buildAsync()

        controllerFuture.addListener(
            { initController(streams, controls) },
            MoreExecutors.directExecutor()
        )
    }

    override fun release() = MediaController.releaseFuture(controllerFuture)

    private fun initController(streams: List<Stream>, controls: StyledPlayerControls) {
        controls.player = requireController()
        for (stream in streams) {
            requireController().addMediaItem(
                MediaItem.Builder()
                    .setMediaId(stream.id)
                    .setMediaMetadata(
                        MediaMetadata.Builder()
                            .setArtworkData(toByteArray(stream.smallImgRes), PICTURE_TYPE_OTHER)
                            .setTitle(stream.title)
                            .setArtist(stream.desc)
                            .build()
                    ).build()
            )
        }
    }

    private fun requireController(): MediaController = controller!!
}

As you can see, not much is happening except the media items are being added when the app is launched, this triggers the media notification creation.

I'm running this on a Pixel 4a with Android 11.
Using media3 version: 1.0.0-alpha01

Feature request (fade and crossfade audio))

It would be extremely helpful to provide fade in/FadeOut and crossfade audio feature. Since current implementation of media3 supports only one Exoplayer, hence crossFade seems not possible.
Since this is the first release, it would be easy to fix it here and add the said feature.

currentMediaItem.mediaId is empty during playback

After assigning a mediaId to a mediaItem, during playback the currentMediaItem.mediaId is at first there then it is empty, I require the mediaItem mediaId as i am playing media from different sources and this identifies them

Notification reappears after closing app when media is paused.

Media notification reappears when media is paused and its buttons not working when the app is removed from recents[demo-session].
Steps to reproduce:
1- Run demo-session app.
2- Play some media.
3- Pause it.
4- Remove the app from recents.
5- Notification will reappear and the media button does nothing.

See video here.

Device: Xiaomi mi a2 android 10

How to fetch media urls for media items just in time?

I'm trying to migrate an app from bare ExoPlayer to Media3. The app heavily uses background audio playback, so I thought that I should go with session demo.

Due to our backend limitations, we have to request media url from server separately for each media item. Previously we were using exoPlayer.setMediaSource() with a ConcatenatingMediaSource that relied on DataSource that handled just in time fetching logic. I think a pretty similar example can be found here.

As far as I understand, I am supposed to use MediaController for controlling the underlying player to ensure background playback. However, MediaController does not have setMediaSource() and instead has setMediaItems() method.

Personally, I see 2 options and so far they do not seem to be viable:

  1. Use MediaSession.MediaItemFiller & fetch the url in fillInLocalConfiguration() – however, I suspect that when using setMediaItems() it will try to load all the items' urls & furthermore block the main thread
  2. Not to use MediaController & instead directly interact with setMediaSource() of ExoPlayer that would be stored as a field in MediaLibraryService & used to create a MediaLibrarySession. However, I think this will create a ton of issues for syncing the media session and ensuring background work

So could you please explain me how do I implement just in time links fetching with Media3?

Page size is ignored when requested page is 0

When using MediaBrowserCompat#subscribe(...) with an options bundle containing MediaBrowserCompat.EXTRA_PAGE equal 0, any MediaBrowserCompat.EXTRA_PAGE_SIZE will be ignored and Integer.MAX_VALUE will be used.

MediaLibraryServiceLegacyStub checks if the page is > 0, and it ignores all options if the page not valid.

Steps to reproduce:

  1. Query a MediaLibraryService using MediaBrowserCompat#subscribe(...) with an options bundle:

EXTRA_PAGE = 0
EXTRA_PAGE_SIZE = 20 /* Any value > 0 can be used */

  1. Log the page and pageSize values received by our MediaLibrarySessionCallback#onGetChildren

  2. The values received will be page = 0 and pageSize = Integer.MAX_VALUE

Expected behavior

From MediaBrowserCompat documentation, 0 should be a valid page:

The value of EXTRA_PAGE should be greater than or equal to 0

Also, I don't know if this behavior is documented, but I would expect the EXTRA_PAGE_SIZE value to be used even if the page value is wrong (e.g., -1).

MediaSession subscription

In MediaCompat when subscribing to a MediaSession you would pass a SubscriptionCallback and when unsubscribing you could also pass the same callback.
Now, in Media3 the callback is set only once in the initialization of the MediaSession, so if a caller wants to subscribe to multiple id's he has to know in advance what id's to subscribe to, or you can have a "callback manager" to handle multiple subscriptions.

My target is to be able to get a kotlin flow of all the children which now is quite impossible.

I'm suggesting to pass this callback in the subscribe function.

MediaSourceFactory for different content types combined with the dynamic stream links

We're currently improving out media architecture and there are obviously some things that can be done better. Currently, we support HLS & progressive content (DASH is also coming soon), so we need the Player to know what content it's going to play.

Normally, the player is able to figure out the content type itself from the URL when DefaultMediaSourceFactory is used. It takes a look at the item url in createMediaSource(), determines the content type, and based on that loads the correspondent MediaSourceFactory using reflection. So, if the MediaItem url ends with .m3u8, HlsMediaSourceFactory will be used.

However, this approach is not viable for us, since we're fetching the stream links dynamically (more on that here). So the url in MediaItem is not yet a valid one (it contains just information to fetch the link, smth like app://play?media_id=1). The actual url is fetched using ResolvingDataSource.

Previously we were sending an additional field in our media responses from the backend that contained the available transport methods (HLS, DASH etc). Based on that, we were creating a correspondent MediaSourceFactory on the fly (that MediaSourceFactory was created with ResolvingDataSource). However, this seems to be a sort of hack that we'd like to avoid to make our app more scalable (not to mention that this does not fit into media3 architecture at all)

After researching for some time, I've come with an idea. Smth relatively similar was already done here. So I decided to create a custom MediaSource extending CompositeMediaSource. It looks smth like this (unimportant parts are omitted):

class DynamicMediaSource(
    private val mediaItem: MediaItem,
    private val resolvingDataSourceFactory: ResolvingDataSource.Factory,
    private val delegate: DefaultMediaSourceFactory
) : CompositeMediaSource<Int>() {

    private val loader = Loader("-DynamicSource")

    private var actualSource: MediaSource? = null

    override fun prepareSourceInternal(mediaTransferListener: TransferListener?) {
        super.prepareSourceInternal(mediaTransferListener)

        val loadable = ParsingLoadable<String>(
            resolvingDataSourceFactory.createDataSource(),
            mediaItem.localConfiguration?.uri ?: return,
            C.DATA_TYPE_MEDIA_INITIALIZATION,
            StreamLinkParser()
        )

        val loaderCallback = object : Loader.Callback<ParsingLoadable<String>> {
            override fun onLoadCompleted(
                loadable: ParsingLoadable<String>,
                elapsedRealtimeMs: Long,
                loadDurationMs: Long
            ) {
                val mediaItemWithStreamLink = mediaItem.buildUpon()
                    .setUri(loadable.result)
                    .build()

                val actualMediaSource = delegate.createMediaSource(mediaItemWithStreamLink)

                actualSource = actualMediaSource

                prepareChildSource(null, actualMediaSource)
            }
        }

        loader.startLoading(
            loadable,
            loaderCallback,
            3
        )
    }

    override fun getMediaItem(): MediaItem {
        return mediaItem
    }

    override fun createPeriod(
        id: MediaSource.MediaPeriodId,
        allocator: Allocator,
        startPositionUs: Long
    ): MediaPeriod {
        return actualSource?.createPeriod(id, allocator, startPositionUs) ?: MaskingMediaPeriod(
            id,
            allocator,
            startPositionUs
        )
    }

    override fun releasePeriod(mediaPeriod: MediaPeriod) {
        actualSource?.releasePeriod(mediaPeriod)
    }

    override fun onChildSourceInfoRefreshed(
        id: Int?,
        mediaSource: MediaSource,
        timeline: Timeline
    ) = refreshSourceInfo(timeline)


    private inner class StreamLinkParser : ParsingLoadable.Parser<String> {
        override fun parse(uri: Uri, inputStream: InputStream): String = uri.toString()
    }

}

As you can see, it extends CompositeMediaSource & with the help of the Loader resolves the actual url. After that, the actual MediaSource is created using the wrapped DefaultMediaSourceFactory & child source is prepared.

This seems to be working, however, there are some things that do not feel right for me:

  1. Passing a MediaSourceFactory to the DynamicMediaSource (I'm not sure if it's ok for MediaSource to call MediaSourceFactory methods)
  2. Overall stability of this approach – I am afraid that at some point I'm going to shoot myself in the foot with this

So is there any better way to achieve what I described or should I go with the code above?

How to customize media button handlers ?

I can't customize media button handlers. I should be able to override onMediaButtonEvent, also onStartCommand called with intent with null action when pressing on media button from [bluetooth headset]. It is called after the media is played or paused so there is no chance to customize buttons behavior.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.