Skip to content

Feat/media3 newarch #2478

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

Open
wants to merge 57 commits into
base: main
Choose a base branch
from
Open

Feat/media3 newarch #2478

wants to merge 57 commits into from

Conversation

jspizziri
Copy link
Collaborator

@jspizziri jspizziri commented Jun 25, 2025

  • Migrates the library to use media3 on Android.
  • Migrates the library to be compatible with the newArch.
  • Removes support for the legacy architecture.
  • Removes deprecated functionality.

TODO:

  • Add web example
  • Fix issues with Remote playback events not firing.
  • Test web
  • Upgrade example to [email protected]
  • Fix iOS CI pipeline

behenate and others added 30 commits November 4, 2024 13:41
Node 22 has entered LTS as of October 2024
Brings in media3 & android-auto functionalities from https://github.com/lovegaoshi/react-native-track-player, leaving out widget & audio fading functionalities
- show jump backward & forward buttons in notification
- remove compactCapabilities
- rename BaseAudioPlayer#player to BaseAudioPlayer#forwardingPlayer
@Bogdastotel
Copy link

when can we expect this pr to be approved?

@ngambmicheal
Copy link

It works fine on iOS but on Android it throws java.lang.NullPointerException: java.lang.NullPointerException when calling TrackPlayer.add. Does anyone have this error like me?

Me too, i have the same issue. I tried to upgrade the react-native, no luck It works very well on ios though.

It's working for me, i had to remove maxCacheSize from the setup. Looks like there is an issue with the caching

  "nativeStackAndroid": [
    {
      "lineNumber": 125,
      "file": "MediaFactory.kt",
      "methodName": "enableCaching",
      "class": "com.doublesymmetry.kotlinaudio.players.components.MediaFactory"
    },
    {
      "lineNumber": 82,
      "file": "MediaFactory.kt",
      "methodName": "createMediaSource",
      "class": "com.doublesymmetry.kotlinaudio.players.components.MediaFactory"
    },
    {
      "lineNumber": 1971,
      "file": "ExoPlayerImpl.java",
      "methodName": "createMediaSources",
      "class": "androidx.media3.exoplayer.ExoPlayerImpl"
    },
    {
      "lineNumber": 651,
      "file": "ExoPlayerImpl.java",
      "methodName": "addMediaItems",
      "class": "androidx.media3.exoplayer.ExoPlayerImpl"
    },
    {
      "lineNumber": 151,
      "file": "QueuedAudioPlayer.kt",
      "methodName": "add",
      "class": "com.doublesymmetry.kotlinaudio.players.QueuedAudioPlayer"
    },
    {
      "lineNumber": 370,
      "file": "MusicService.kt",
      "methodName": "add",
      "class": "com.doublesymmetry.trackplayer.service.MusicService"
    },
    {
      "lineNumber": 317,
      "file": "MusicModule.kt",
      "methodName": "invokeSuspend",
      "class": "com.doublesymmetry.trackplayer.module.MusicModule$add$1"
    },
    {
      "lineNumber": 8,
      "file": null,
      "methodName": "invoke",
      "class": "com.doublesymmetry.trackplayer.module.MusicModule$add$1"
    },
    {
      "lineNumber": 2,
      "file": null,
      "methodName": "invoke",
      "class": "com.doublesymmetry.trackplayer.module.MusicModule$add$1"
    },
    {
      "lineNumber": 627,
      "file": "MusicModule.kt",
      "methodName": "invokeSuspend",
      "class": "com.doublesymmetry.trackplayer.module.MusicModule$launchInScope$1"
    },
    {
      "lineNumber": 33,
      "file": "ContinuationImpl.kt",
      "methodName": "resumeWith",
      "class": "kotlin.coroutines.jvm.internal.BaseContinuationImpl"
    },
    {
      "lineNumber": 104,
      "file": "DispatchedTask.kt",
      "methodName": "run",
      "class": "kotlinx.coroutines.DispatchedTask"
    },
    {
      "lineNumber": 995,
      "file": "Handler.java",
      "methodName": "handleCallback",
      "class": "android.os.Handler"
    },
    {
      "lineNumber": 103,
      "file": "Handler.java",
      "methodName": "dispatchMessage",
      "class": "android.os.Handler"
    },
    {
      "lineNumber": 248,
      "file": "Looper.java",
      "methodName": "loopOnce",
      "class": "android.os.Looper"
    },
    {
      "lineNumber": 338,
      "file": "Looper.java",
      "methodName": "loop",
      "class": "android.os.Looper"
    },
    {
      "lineNumber": 9067,
      "file": "ActivityThread.java",
      "methodName": "main",
      "class": "android.app.ActivityThread"
    },
    {
      "lineNumber": -2,
      "file": "Method.java",
      "methodName": "invoke",
      "class": "java.lang.reflect.Method"
    },
    {
      "lineNumber": 593,
      "file": "RuntimeInit.java",
      "methodName": "run",
      "class": "com.android.internal.os.RuntimeInit$MethodAndArgsCaller"
    },
    {
      "lineNumber": 932,
      "file": "ZygoteInit.java",
      "methodName": "main",
      "class": "com.android.internal.os.ZygoteInit"
    }
  ],
  "name": "java.lang.NullPointerException",
  "userInfo": null,
  "code": "runtime_exception"
}
 (NOBRIDGE) ERROR  [TrackPlayerService] Error in setPlaylist: [Error: TrackPlayer.add() failed: java.lang.NullPointerException]
 (NOBRIDGE) ERROR  [TrackPlayerService] Error details: {}
 (NOBRIDGE) ERROR  [TrackPlayerService] Error in playSingleTrack: [Error: TrackPlayer.add() failed: java.lang.NullPointerException]
 (NOBRIDGE) ERROR  Error playing single track: [Error: TrackPlayer.add() fail

@doughsay
Copy link

Ok, yeah I can also reproduce the same crash when calling TrackPlayer.add while having the maxCacheSize setting enabled. But it only happens for streaming URLs for me, if I load a locally downloaded file using expo-file-system it doesn't crash.

@deveshp
Copy link

deveshp commented Jul 15, 2025

Checked out a few different streaming radios and some seem to not be working on android.

Example of streams that work : https://icecast.walmradio.com:8443/classic, https://mangoradio.stream.laut.fm/mangoradio

Example of streams that don't : https://airhlspush.pc.cdn.bitgravity.com/httppush/hlspbaudio005/hlspbaudio005_Auto.m3u8, http://stream.live.vc.bbcmedia.co.uk/bbc_world_service

@deveshp
Copy link

deveshp commented Jul 15, 2025

Checked out a few different streaming radios and some seem to not be working on android.

Example of streams that work : https://icecast.walmradio.com:8443/classic, https://mangoradio.stream.laut.fm/mangoradio

Example of streams that don't : https://airhlspush.pc.cdn.bitgravity.com/httppush/hlspbaudio005/hlspbaudio005_Auto.m3u8, http://stream.live.vc.bbcmedia.co.uk/bbc_world_service

Alright, used these steps, and now the streams load except for the m3u stream.

  await TrackPlayer.reset();
  await TrackPlayer.load(track);
  await TrackPlayer.play();

@qza1800
Copy link

qza1800 commented Jul 16, 2025

I have a strange issue with notification center on Android. It works perfect with main (with RN 0.74.3) but not with newarch (with RN 0.80.1)

I used the same options

await TrackPlayer.updateOptions({
    android: {
      appKilledPlaybackBehavior: DefaultAudioServiceBehaviour,
    },
    capabilities: [
      Capability.Play,
      Capability.Pause,
      Capability.SkipToNext,
      Capability.SkipToPrevious,
      Capability.JumpForward,
      Capability.JumpBackward,
    ],
    compactCapabilities: [
      Capability.Play,
      Capability.Pause,
      Capability.SkipToNext,
    ],
    progressUpdateEventInterval: 10,
  });
  • main (with RN 0.74.3)
    Works perfect

    • Full:
    image
    • Compact:
    image
  • newarch (with RN 0.80.1)
    Only RemoteNext and RemotePrevious work, RemotePause and RemotePlay not (nothing fired when clicked), (also RemoteJumpForward and RemotePrevious cannot be confirmed because they are not rendered)

    • Full:
    image
    • Compact:
    image

@ega65
Copy link

ega65 commented Jul 17, 2025

Hello Guys, I would like to help and test it on expo custom dev, but the only way i know to install a library is with npm install or npx expo install.. ..any clue how to replace my RNTP 4 by this what seems to be a nice update ? ;)

@vmsilva
Copy link

vmsilva commented Jul 17, 2025

Hello Guys, I would like to help and test it on expo custom dev, but the only way i know to install a library is with npm install or npx expo install.. ..any clue how to replace my RNTP 4 by this what seems to be a nice update ? ;)

I've created an npm lib to use till next update, here url https://www.npmjs.com/package/@vmsilva/react-native-track-player

@aalekz
Copy link

aalekz commented Jul 18, 2025

Using this in my app running Expo 53 and works great with the new architecture, great work!

I have one question about this commit though: b11736b

It removes both the code emitting MetadataTimedReceived events as well as the deprecated EventType.PlaybackMetadataReceived. Because of this I'm having issues getting the in stream metadata, by adding back the timed metadata event listener on init and a simpler version of handleAudioPlayerTimedMetadataReceived removing the usage of the deprecated EventType.PlaybackMetadataReceived fixes the issue. See example below:

// Trackplayer init()
player.event.receiveTimedMetadata.addListener(self, handleAudioPlayerTimedMetadataReceived)

func handleAudioPlayerTimedMetadataReceived(metadata: [AVTimedMetadataGroup]) {
        let metadataItems = MetadataAdapter.convertToGroupedMetadata(metadataGroups: metadata);
        emit(event: EventType.MetadataTimedReceived, body: ["metadata": metadataItems])
}

I believe this might have been an unintended change, but not sure :)

@ega65
Copy link

ega65 commented Jul 18, 2025

Hello Guys, I would like to help and test it on expo custom dev, but the only way i know to install a library is with npm install or npx expo install.. ..any clue how to replace my RNTP 4 by this what seems to be a nice update ? ;)

I've created an npm lib to use till next update, here url https://www.npmjs.com/package/@vmsilva/react-native-track-player

Thanks a lot for the npm, Obrigado ;) !!!

I have installed it without problem... ...altought I use the event "playback-metadata-received" Is there a reason why it hasn't been implemented in this package ? Or did I miss something ? => Ok, i understand "playback-metadata-received" was already deprecated in RNTP 4.. ..but was still working on Expo51, but not anymore on 53 ?!?! For my program, not getting metadata (artist, song, artwork) makes it useless... :( Any idea how to get these information ??

I use :

 TrackPlayer.addEventListener(
    "playback-metadata-received",
    async (metadataReceived) => {
      let artworkFromJson = URI_IMAGE_DEFAULT_PODCAST; // valeur initiale par défaut

      try {
        const response = await fetch(
          `${URI_JSON_STATUS}?_=${new Date().getTime()}`
        );
        const data = await response.json();
        if (data.current_track && data.current_track.artwork_url_large) {
          artworkFromJson = data.current_track.artwork_url_large;
          //console.log("artworkFromJson", artworkFromJson);
        }
      } catch (error) {
        console.error(
          "Erreur lors de la récupération de l'artwork depuis le JSON:",
          error
        );
      }

      const newMetadata = {
        title: metadataReceived.title ? metadataReceived.title : "Radio",
        artist: metadataReceived.artist ? metadataReceived.artist : "Live",
        artwork: artworkFromJson ? artworkFromJson : URI_IMAGE_DEFAULT_PODCAST,
      };

      

      console.log("newMetadata", newMetadata);

      // Update the TrackPlayer with the new metadata
      let trackObject = await TrackPlayer.getTrack(0);
      if (trackObject.isContinuous) {
        await TrackPlayer.updateMetadataForTrack(0, newMetadata);
      }

      DeviceEventEmitter.emit("metadataUpdated", newMetadata);
    }
  );

@dodgex
Copy link

dodgex commented Jul 20, 2025

Is it intended, that compactCapabilities has been removed from UpdateOptions? I wonder especially, as the docs/versioned_docs/version-5.0/api/objects/update-options.md and docs/versioned_docs/version-5.0/basics/getting-started.md still mention that field as if it still is supported.

@ega65
Copy link

ega65 commented Jul 20, 2025

Using this in my app running Expo 53 and works great with the new architecture, great work!

I have one question about this commit though: b11736b

It removes both the code emitting MetadataTimedReceived events as well as the deprecated EventType.PlaybackMetadataReceived. Because of this I'm having issues getting the in stream metadata, by adding back the timed metadata event listener on init and a simpler version of handleAudioPlayerTimedMetadataReceived removing the usage of the deprecated EventType.PlaybackMetadataReceived fixes the issue. See example below:

// Trackplayer init()
player.event.receiveTimedMetadata.addListener(self, handleAudioPlayerTimedMetadataReceived)

func handleAudioPlayerTimedMetadataReceived(metadata: [AVTimedMetadataGroup]) {
        let metadataItems = MetadataAdapter.convertToGroupedMetadata(metadataGroups: metadata);
        emit(event: EventType.MetadataTimedReceived, body: ["metadata": metadataItems])
}

I believe this might have been an unintended change, but not sure :)

Hi ! It seems great !! Not sure to fully understand what you have done. I have the same problem...
...can you be more precise where you've added these lines ???

@scottgrobinson
Copy link

scottgrobinson commented Jul 21, 2025

Incase it helps someone else... Spent hours working out why I couldn't pause from the android system bar drawer. Got AI involved. We made a mess of it. Got there in the end though - I needed to add an event for RemotePlayPause which I don't see in the docs or codebase anywhere.

service.js

  TrackPlayer.addEventListener(Event.RemotePlayPause, async () => {
    const state = await TrackPlayer.getPlaybackState();
    if (state.state === State.Playing) {
      await TrackPlayer.pause();
    } else {
      await TrackPlayer.play();
    }
  });

This MAY not be specific to this new media3-newarch patch, but it's the one I'm using...

@jspizziri
Copy link
Collaborator Author

@dodgex @qza1800 ,

compactCapabilities are no longer supported. I've just pushed a commit that removes the out-dated documentation.

@jspizziri
Copy link
Collaborator Author

jspizziri commented Jul 21, 2025

@ngambmicheal @doughsay , I've confirmed the issue with maxCacheSize. I'll try to get to the bottom of it.

EDIT: the issue seems to be related to the custom MediaFactory.

@lrobak0
Copy link

lrobak0 commented Jul 21, 2025

Hi, I noticed that the following event listeners are not working as expected:

TrackPlayer.addEventListener(Event.RemotePause, () => { ... });
TrackPlayer.addEventListener(Event.RemotePlay, () => { ... });

They don't seem to be triggered when interacting with the system media controls. (play/pause buttons)

@scottgrobinson
Copy link

Hi, I noticed that the following event listeners are not working as expected:

TrackPlayer.addEventListener(Event.RemotePause, () => { ... });

TrackPlayer.addEventListener(Event.RemotePlay, () => { ... });

They don't seem to be triggered when interacting with the system media controls. (play/pause buttons)

See my message above. I think you need to use PlayPause

@jspizziri
Copy link
Collaborator Author

@ngambmicheal @doughsay I believe that 349eff0 should fix your issue. Please test and LMK.

@jspizziri
Copy link
Collaborator Author

@deveshp , I think there's likely some errors with your streams. All the streams we have configured in the test app are working like they have prior to this change. You might try debugging the sources themselves for issues, and also testing them in the main example app to confirm the issue is or is not present outside of this branch.

@pigeonmal
Copy link

Events on playback service doesn't work anymore, i used Event.PlaybackActiveTrackChanged.
But work on react componenet

  let lastEvent: any;

  // Called twice for first time ?? (wtf)
  TrackPlayer.addEventListener(
    Event.PlaybackActiveTrackChanged,
    async ({index, track, lastIndex}) => {
      if (
        lastEvent &&
        lastEvent.lastIndex == lastIndex &&
        index == lastEvent.index &&
        track?.id == lastEvent.id
      ) {
        // DUPLICATE EVENT
        return;
      }
      lastEvent = {
        index,
        lastIndex,
        id: track?.id,
      };
      if (abortController) {
        abortController.abort();
      }
      const newTrack = track;
      if (newTrack) {
        const url = newTrack.url;
        const currId = newTrack.id;
        const hasUrl = url != null;
        const isHttpUrl = hasUrl && url.startsWith('http');

        const newAbC = new AbortController();
        abortController = newAbC;

        try {
          const isDownloaded = await getSongDownload(currId);
          if (!newAbC.alive) return;
          if (isDownloaded) {
            if (hasUrl && !isHttpUrl) return; // already have url
            newTrack.url = FileSystemManager.getMusicFilePath(currId);
            newTrack.duration = isDownloaded;
            await TrackPlayer.load(newTrack);
          } else {
            const cachedSong = YMusicPlayerCache.get(currId);
            if (cachedSong && cachedSong.url === url) {
              return;
            }
            const streamInfo =
              cachedSong || (await getStreamInfo(currId, newAbC));
            if (streamInfo && newAbC.alive) {
              newTrack.url = streamInfo.url;
              newTrack.duration = streamInfo.duration;
              await TrackPlayer.load(newTrack);
            }
          }
        } catch (err) {}
      }
    },
  );
}

@lrobak0
Copy link

lrobak0 commented Jul 22, 2025

Hi, I noticed that the following event listeners are not working as expected:
TrackPlayer.addEventListener(Event.RemotePause, () => { ... });
TrackPlayer.addEventListener(Event.RemotePlay, () => { ... });
They don't seem to be triggered when interacting with the system media controls. (play/pause buttons)

See my message above. I think you need to use PlayPause

...also doesn't work. I'm using react native CLI and tested it on Android 15

@jspizziri jspizziri force-pushed the feat/media3-newarch branch from a3528d2 to f3fc4d5 Compare July 22, 2025 12:57
@doughsay
Copy link

doughsay commented Jul 23, 2025

@ngambmicheal @doughsay I believe that 349eff0 should fix your issue. Please test and LMK.

Unfortunately no, it seems to still crash for me when using caching. I get [java.lang.NullPointerException: java.lang.NullPointerException] and having a hard time getting any more detail than that. I wasn't actually using the maxCacheSize option, so this doesn't affect me, I just checked on it to see if I could reproduce what others saw.

I've also since noticed that several of the remote events no longer fire (on Android; iOS untested). Specifically I'm seeing RemotePlay, RemotePause and RemoteDuck never firing. Some messages above claim using RemotePlayPause works, but I haven't seen that one fire either.

@jspizziri
Copy link
Collaborator Author

jspizziri commented Jul 24, 2025

@doughsay ,

Unfortunately no, it seems to still crash for me when using caching. I get [java.lang.NullPointerException: java.lang.NullPointerException] and having a hard time getting any more detail than that. I wasn't actually using the maxCacheSize option, so this doesn't affect me, I just checked on it to see if I could reproduce what others saw.

If you're getting the crash still I'd recommend double checking that you've pull down the latest version of the branch (it's been rebased so you'll need to do a reset). I'm no longer able to reproduce this issue after my recent changes, so further investigation will require new information.

I've also since noticed that several of the remote events no longer fire (on Android; iOS untested). Specifically I'm seeing RemotePlay, RemotePause and RemoteDuck never firing. Some messages above claim using RemotePlayPause works, but I haven't seen that one fire either.

I'll take a look at this.

EDIT: I've confirmed the issue with Remote events not firing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.