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

Feat: media3 & android auto #2403

Draft
wants to merge 41 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
5878c5b
chore: improve bridgeless interop layer compatibility
behenate Apr 5, 2024
d918b8e
fix: broken events in bridgeless mode (React Native New Architecture)
robik Sep 13, 2024
f369f4a
chore: update node to v22
puckey Nov 4, 2024
82d635a
example: update to react-native 0.76.1
puckey Nov 5, 2024
4336358
android: inline KotlinAudio
puckey Nov 7, 2024
edcb7d3
hooks: fix circular dependency
puckey Nov 11, 2024
ef72936
chore: remove deprecated functionalities
puckey Nov 11, 2024
67220ad
feat: initial media3 functionality
puckey Nov 12, 2024
edd6e1a
android: various
puckey Nov 15, 2024
73abf8a
example: update
puckey Nov 15, 2024
825af41
android: remove forwarding player
puckey Nov 18, 2024
59ebf7e
example: add android:adb-reverse script
puckey Nov 18, 2024
067ca53
example: update react-native to 0.76.2
puckey Nov 18, 2024
fbf550c
android: default mediaId to audioItem.audioUrl
puckey Nov 18, 2024
61c8f1f
android: rename timber tags to RNTP
puckey Nov 18, 2024
2dc637f
fix: catch currentTrack null
lovegaoshi Nov 8, 2024
26a5826
feat: onStartCommandIntentValid
lovegaoshi Nov 16, 2024
fdeb546
feat: onStartCommandIntentValid
lovegaoshi Nov 16, 2024
bdf3c87
remove deprecated clearNowPlayingMetadata method
puckey Dec 3, 2024
a6b2a4f
android: rename variable mediaSource to mediaItem in replaceItem method
puckey Dec 3, 2024
26582f5
android: inline FocusManager & bring back ForwardingPlayer
puckey Dec 3, 2024
a6e6e3b
android: simplify getActiveTrack
puckey Dec 3, 2024
8d25a1c
android: allow currentTrack to be null
puckey Dec 3, 2024
64ba853
android: forward custom commands to forwarding player
puckey Dec 3, 2024
ba6f4c5
android: fix updateMetadataForTrack
puckey Dec 4, 2024
e16669a
android: improve logging
puckey Dec 4, 2024
4b0575f
android: interceptPlayerActionsTriggeredExternally
puckey Dec 4, 2024
753a931
android: rename inner class
puckey Dec 4, 2024
ba6c7e2
android: bring back rating support
puckey Dec 4, 2024
9a80a1a
android: remove deprecated isForegroundService
puckey Dec 4, 2024
5a18d3d
android: clean up
puckey Dec 4, 2024
ffa2ab9
android: remove custom actions for now
puckey Dec 4, 2024
43a685d
android: replace Log with Timber for logging
puckey Dec 4, 2024
3123e8e
android: remove embedded bitmap functionality for now
puckey Dec 4, 2024
a695c85
android: refactor audioItem2MediaItem to use coroutines for artwork l…
puckey Dec 4, 2024
d3268bd
android: turn AudioItem into a class
puckey Dec 4, 2024
a42ea71
android: remove embedded bitmap remnants
puckey Dec 4, 2024
6fe33ac
android: fix rating class
puckey Dec 4, 2024
eaea230
remove custom buttons for now
puckey Dec 4, 2024
69989cb
android: rename cache directory from APM to RNTP
puckey Dec 4, 2024
c06874e
android: further refactor audio items
puckey Dec 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,11 @@ example/ios/Pods
ios/Pods
ios/RNTrackPlayer.xcworkspace
Podfile.lock

# Yarn
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
2 changes: 1 addition & 1 deletion .node-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
16
22
1 change: 1 addition & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
71 changes: 53 additions & 18 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,32 +1,39 @@
import com.android.Version

buildscript {
repositories {
mavenLocal()
mavenCentral()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.2'
classpath 'com.android.tools.build:gradle:8.7.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10"
}
}

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

def getExtOrIntegerDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['RNTP_' + name]).toInteger()
def getExtOrIntegerDefault(name, defaultInt) {
try {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['RNTP_' + name]).toInteger()
} catch (ignored) {
return defaultInt
}
}

android {
compileSdkVersion getExtOrIntegerDefault('compileSdkVersion')
namespace 'com.doublesymmetry.trackplayer'
compileSdkVersion getExtOrIntegerDefault('compileSdkVersion', 34)

def agpVersion = Version.ANDROID_GRADLE_PLUGIN_VERSION
if (agpVersion.tokenize('.')[0].toInteger() >= 7) {
namespace "com.doublesymmetry.trackplayer"
}
defaultConfig {
minSdkVersion getExtOrIntegerDefault('minSdkVersion') // RN's minimum version
targetSdkVersion getExtOrIntegerDefault('targetSdkVersion')
minSdkVersion getExtOrIntegerDefault('minSdkVersion', 23) // RN's minimum version
targetSdkVersion getExtOrIntegerDefault('targetSdkVersion', 34)

versionCode 300
versionName '3.0'

consumerProguardFiles 'proguard-rules.txt'
}
Expand All @@ -35,6 +42,12 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
if (rootProject.ext.has("native")) {
kotlinOptions {
// HACK: does RN auto sets to 17? or am i crazy?
jvmTarget = "1.8"
}
}
}

repositories {
Expand All @@ -50,16 +63,38 @@ repositories {
}

dependencies {
implementation 'com.github.doublesymmetry:kotlinaudio:v2.1.0'
// used when building against local maven
// implementation "com.github.doublesymmetry:kotlin-audio:2.1.0"

//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+"

if (rootProject.ext.has("native")) {
implementation "com.facebook.react:react-android:+"
} else {
implementation "com.facebook.react:react-native:+"
}
// Make sure we're using androidx
implementation "androidx.core:core-ktx:1.9.0"
implementation "androidx.core:core-ktx:1.13.1"
implementation "androidx.localbroadcastmanager:localbroadcastmanager:1.1.0"
implementation "androidx.lifecycle:lifecycle-process:2.5.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.3"
implementation "androidx.lifecycle:lifecycle-process:2.8.6"
implementation 'androidx.media:media:1.7.0'

implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.6")
implementation("androidx.activity:activity-compose:1.9.3")
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")

implementation("androidx.media3:media3-exoplayer:1.4.1")
implementation("androidx.media3:media3-session:1.4.1")
implementation("androidx.media3:media3-ui:1.4.1")
implementation("androidx.media3:media3-exoplayer-hls:1.4.1")
implementation("androidx.media3:media3-exoplayer-dash:1.4.1")
implementation("androidx.media3:media3-exoplayer-smoothstreaming:1.4.1")
implementation("androidx.media3:media3-common:1.4.1")

implementation "com.google.guava:guava:33.0.0-android"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.8.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3"

implementation 'io.coil-kt:coil:2.4.0'
api 'com.jakewharton.timber:timber:5.0.1'

implementation 'androidx.test:rules:1.6.1'
implementation 'jp.wasabeef.transformers:coil:1.0.6'
}
10 changes: 8 additions & 2 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,24 @@

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

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

<application>

<!-- The main service, handles playback, playlists and media buttons -->
<service android:name="com.doublesymmetry.trackplayer.service.MusicService" android:enabled="true" android:exported="true" android:foregroundServiceType="mediaPlayback">
<service
android:name="com.doublesymmetry.trackplayer.service.MusicService"
android:enabled="true"
android:exported="true"
android:foregroundServiceType="mediaPlayback">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
<action android:name="androidx.media3.session.MediaLibraryService" />
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>

</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.doublesymmetry.kotlinaudio.event

class EventHolder internal constructor(private val playerEventHolder: PlayerEventHolder) {
val audioItemTransition
get() = playerEventHolder.audioItemTransition

val onAudioFocusChanged
get() = playerEventHolder.onAudioFocusChanged

val onCommonMetadata
get() = playerEventHolder.onCommonMetadata

val onTimedMetadata
get() = playerEventHolder.onTimedMetadata

val onPlayerActionTriggeredExternally
get() = playerEventHolder.onPlayerActionTriggeredExternally

val playbackEnd
get() = playerEventHolder.playbackEnd

val playWhenReadyChange
get() = playerEventHolder.playWhenReadyChange

val stateChange
get() = playerEventHolder.stateChange

val playbackError
get() = playerEventHolder.playbackError
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package com.doublesymmetry.kotlinaudio.event

import androidx.annotation.OptIn
import androidx.media3.common.MediaMetadata
import androidx.media3.common.Metadata
import androidx.media3.common.util.UnstableApi
import com.doublesymmetry.kotlinaudio.models.*
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch

class PlayerEventHolder {
private val coroutineScope = MainScope()

private var _stateChange = MutableSharedFlow<AudioPlayerState>(1)
var stateChange = _stateChange.asSharedFlow()

private var _playbackEnd = MutableSharedFlow<PlaybackEndedReason?>(1)
var playbackEnd = _playbackEnd.asSharedFlow()

private var _playbackError = MutableSharedFlow<PlaybackError>(1)
var playbackError = _playbackError.asSharedFlow()

private var _playWhenReadyChange = MutableSharedFlow<PlayWhenReadyChangeData>(1)
/**
* Use these events to track when [com.doublesymmetry.kotlinaudio.players.BaseAudioPlayer.playWhenReady]
* changes.
*/
var playWhenReadyChange = _playWhenReadyChange.asSharedFlow()

private var _audioItemTransition = MutableSharedFlow<AudioItemTransitionReason?>(1)

/**
* Use these events to track when and why an [AudioItem] transitions to another.
*
* Examples of an audio transition include changes to [AudioItem] queue, an [AudioItem] on repeat, skipping an [AudioItem], or simply when the [AudioItem] has finished.
*/
var audioItemTransition = _audioItemTransition.asSharedFlow()

private var _positionChanged = MutableSharedFlow<PositionChangedReason?>(1)
var positionChanged = _positionChanged.asSharedFlow()

private var _onAudioFocusChanged = MutableSharedFlow<FocusChangeData>(1)
var onAudioFocusChanged = _onAudioFocusChanged.asSharedFlow()

private var _onCommonMetadata = MutableSharedFlow<MediaMetadata>(1)
var onCommonMetadata = _onCommonMetadata.asSharedFlow()

private var _onTimedMetadata = MutableSharedFlow<Metadata>(1)
var onTimedMetadata = _onTimedMetadata.asSharedFlow()

private var _onPlayerActionTriggeredExternally = MutableSharedFlow<MediaSessionCallback>()

/**
* Use these events to track whenever a player action has been triggered from an outside source.
*
* The sources can be: media buttons on headphones, Android Wear, Android Auto, Google Assistant, media notification, etc.
*
* For this observable to send events, set [interceptPlayerActionsTriggeredExternally][com.doublesymmetry.kotlinaudio.models.PlayerConfig.interceptPlayerActionsTriggeredExternally] to true.
*/
var onPlayerActionTriggeredExternally = _onPlayerActionTriggeredExternally.asSharedFlow()

internal fun updateAudioPlayerState(state: AudioPlayerState) {
coroutineScope.launch {
_stateChange.emit(state)
}
}

internal fun updatePlaybackEndedReason(reason: PlaybackEndedReason) {
coroutineScope.launch {
_playbackEnd.emit(reason)
}
}

internal fun updatePlayWhenReadyChange(playWhenReadyChange: PlayWhenReadyChangeData) {
coroutineScope.launch {
_playWhenReadyChange.emit(playWhenReadyChange)
}
}

internal fun updateAudioItemTransition(reason: AudioItemTransitionReason) {
coroutineScope.launch {
_audioItemTransition.emit(reason)
}
}

internal fun updatePositionChangedReason(reason: PositionChangedReason) {
coroutineScope.launch {
_positionChanged.emit(reason)
}
}

internal fun updateOnAudioFocusChanged(isPaused: Boolean, isPermanent: Boolean) {
coroutineScope.launch {
_onAudioFocusChanged.emit(FocusChangeData(isPaused, isPermanent))
}
}

internal fun updateOnCommonMetadata(metadata: MediaMetadata) {
coroutineScope.launch {
_onCommonMetadata.emit(metadata)
}
}

@OptIn(UnstableApi::class)
internal fun updateOnTimedMetadata(metadata: Metadata) {
coroutineScope.launch {
_onTimedMetadata.emit(metadata)
}
}

internal fun updatePlaybackError(error: PlaybackError) {
coroutineScope.launch {
_playbackError.emit(error)
}
}

internal fun updateOnPlayerActionTriggeredExternally(callback: MediaSessionCallback) {
coroutineScope.launch {
_onPlayerActionTriggeredExternally.emit(callback)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.doublesymmetry.kotlinaudio.models

enum class AudioContentType {
MUSIC,
SPEECH,
SONIFICATION,
MOVIE,
UNKNOWN
}

Loading
Loading