Skip to content

added interruptionObserver #157

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

Draft
wants to merge 17 commits into
base: media3_service_analysis
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 8 additions & 5 deletions packages/player/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version '1.0.4'

buildscript {
ext.kotlin_version = '1.9.20'
ext.media3_version = '1.4.1'
ext.media3_version = '1.5.1'
repositories {
google()
mavenCentral()
Expand Down Expand Up @@ -32,7 +32,7 @@ kapt {
}

android {
compileSdkVersion 34
compileSdk = 35

sourceSets {
main.java.srcDirs += 'src/main/kotlin'
Expand All @@ -49,7 +49,7 @@ android {

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1'
implementation "androidx.media:media:1.7.0"
implementation "org.jetbrains.kotlin:kotlin-reflect"
//MEDIA3 DEPENDENCIES
Expand All @@ -60,8 +60,11 @@ dependencies {
implementation "androidx.media3:media3-ui:$media3_version"

// implementation files('/Users/lucastonussi/flutter/bin/cache/artifacts/engine/android-x64/flutter.jar')
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1'

implementation "com.google.code.gson:gson:2.10.1"

// api 'com.google.android.gms:play-services-cast-framework:22.0.0'
// CHROMECAST DEPENDENCIES
implementation 'androidx.mediarouter:mediarouter:1.7.0'
implementation 'androidx.media3:media3-cast:1.5.1'
}
3 changes: 3 additions & 0 deletions packages/player/android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />

<application>
<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="br.com.suamusica.player.CastOptionsProvider" />
<service android:name=".MediaService" android:exported="false" android:foregroundServiceType="mediaPlayback">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
package br.com.suamusica.player

import android.content.Context
import android.util.Log
import androidx.media3.cast.SessionAvailabilityListener
import androidx.media3.common.util.UnstableApi
import androidx.mediarouter.media.MediaControlIntent
import androidx.mediarouter.media.MediaControlIntent.CATEGORY_LIVE_AUDIO
import androidx.mediarouter.media.MediaControlIntent.CATEGORY_LIVE_VIDEO
import androidx.mediarouter.media.MediaControlIntent.CATEGORY_REMOTE_PLAYBACK
import androidx.mediarouter.media.MediaRouteSelector
import androidx.mediarouter.media.MediaRouter
import androidx.mediarouter.media.MediaRouter.UNSELECT_REASON_DISCONNECTED
import androidx.mediarouter.media.RemotePlaybackClient
import com.google.android.gms.cast.*
import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.CastState
import com.google.android.gms.cast.framework.CastStateListener
import com.google.android.gms.cast.framework.Session
import com.google.android.gms.cast.framework.SessionManager
import com.google.android.gms.cast.framework.SessionManagerListener
import com.google.android.gms.common.api.PendingResult
import com.google.android.gms.common.api.Status


@UnstableApi

class CastManager(
castContext: CastContext,
context: Context,
) :
SessionAvailabilityListener,
CastStateListener,
Cast.Listener(),
SessionManagerListener<Session>,
PendingResult.StatusListener {
companion object {
const val TAG = "Chromecast"
}

private var mediaRouter = MediaRouter.getInstance(context)
var isConnected = false
private var sessionManager: SessionManager? = null
private var mediaRouterCallback: MediaRouter.Callback? = null
private var onConnectCallback: (() -> Unit)? = null
private var onSessionEndedCallback: (() -> Unit)? = null
private var alreadyConnected = false
private var cookie: String = ""

init {
castContext.addCastStateListener(this)
sessionManager = castContext.sessionManager

//TODO: pode remover esse callback?
mediaRouterCallback = object : MediaRouter.Callback() {
override fun onRouteAdded(router: MediaRouter, route: MediaRouter.RouteInfo) {
super.onRouteAdded(router, route)
Log.d(TAG, "#NATIVE LOGS CAST ==> Route added: " + route.getName())
}

override fun onRouteRemoved(router: MediaRouter, route: MediaRouter.RouteInfo) {
super.onRouteRemoved(router, route)
Log.d(TAG, "#NATIVE LOGS CAST ==> Route removed: " + route.getName())
}

override fun onRouteChanged(router: MediaRouter, route: MediaRouter.RouteInfo) {
super.onRouteChanged(router, route)
Log.d(TAG, "#NATIVE LOGS CAST ==> Route changed: " + route.getName())
}

override fun onRouteSelected(
router: MediaRouter,
route: MediaRouter.RouteInfo,
reason: Int
) {
Log.d(
TAG,
"#NATIVE LOGS CAST ==> Route selected: " + route.getName() + ", reason: " + reason
)
}

override fun onRouteUnselected(
router: MediaRouter,
route: MediaRouter.RouteInfo,
reason: Int
) {
Log.d(
TAG,
"#NATIVE LOGS CAST ==> Route unselected: " + route.getName() + ", reason: " + reason
)
}
}

val selector: MediaRouteSelector.Builder = MediaRouteSelector.Builder()
.addControlCategory(CATEGORY_REMOTE_PLAYBACK)
//TODO: remover?
mediaRouterCallback?.let {
mediaRouter.addCallback(
selector.build(), it,
MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN
)
}
}

// fun discoveryCast(): List<Map<String, String>> {
// val casts = mutableListOf<Map<String, String>>()
// if (castContext.castState != CastState.NO_DEVICES_AVAILABLE) {
// mediaRouter.routes.forEach {
// if (it.deviceType == DEVICE_TYPE_TV && it.id.isNotEmpty()) {
// casts.add(
// mapOf(
// "name" to it.name,
// "id" to it.id,
// )
// )
// }
// }
// }
// return casts
// }

fun connectToCast(idCast: String) {
val item = mediaRouter.routes.firstOrNull {
it.id.contains(idCast)
}
if (!isConnected) {
if (item != null) {
mediaRouter.selectRoute(item)
return
}
} else {
mediaRouter.unselect(UNSELECT_REASON_DISCONNECTED)
}
}

fun disconnect() {
if (isConnected) {
sessionManager?.endCurrentSession(true)
onSessionEndedCallback?.invoke()
isConnected = false
}
}

// private fun createQueueItem(mediaItem: MediaItem): MediaQueueItem {
// val mediaInfo = createMediaInfo(mediaItem)
// return MediaQueueItem.Builder(mediaInfo).build()
// }
//
// fun queueLoadCast(mediaItems: List<MediaItem>) {
// val mediaQueueItems = mediaItems.map { mediaItem ->
// createQueueItem(mediaItem)
// }
//
// val cookieOk = cookie.replace("CloudFront-Policy=", "{\"CloudFront-Policy\": \"")
// .replace(";CloudFront-Key-Pair-Id=", "\", \"CloudFront-Key-Pair-Id\": \"")
// .replace(";CloudFront-Signature=", "\", \"CloudFront-Signature\": \"") + "\"}"
//
//
// val credentials = JSONObject().put("credentials", cookieOk)
//
//
// val request = sessionManager?.currentCastSession?.remoteMediaClient?.queueLoad(
// mediaQueueItems.toTypedArray(),
// player!!.currentMediaItemIndex,
// 1,
// player.currentPosition,
// credentials,
// )
//
// request?.addStatusListener(this)
// }

// fun loadMediaOld() {
// val media = player!!.currentMediaItem
// val url = media?.associatedMedia?.coverUrl!!
//
// val musictrackMetaData = MediaMetadata(MediaMetadata.MEDIA_TYPE_MUSIC_TRACK)
// musictrackMetaData.putString(MediaMetadata.KEY_TITLE, media.associatedMedia?.name!!)
// musictrackMetaData.putString(MediaMetadata.KEY_ARTIST, media.associatedMedia?.author!!)
// musictrackMetaData.putString(MediaMetadata.KEY_ALBUM_TITLE, "albumName")
// musictrackMetaData.putString("images", url)
//
// media.associatedMedia?.coverUrl?.let {
// musictrackMetaData.addImage(WebImage(Uri.parse(it)))
// }
//
// val mediaInfo =
// MediaInfo.Builder(media.associatedMedia?.url!!)
// .setContentUrl(media.associatedMedia?.url!!)
// .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
// .setMetadata(musictrackMetaData)
// .build()
//
// val cookieOk = cookie.replace("CloudFront-Policy=", "{\"CloudFront-Policy\": \"")
// .replace(";CloudFront-Key-Pair-Id=", "\", \"CloudFront-Key-Pair-Id\": \"")
// .replace(";CloudFront-Signature=", "\", \"CloudFront-Signature\": \"") + "\"}"
//
// val options = MediaLoadOptions.Builder()
//// .setPlayPosition(player.currentPosition)
// .setCredentials(
// cookieOk
// )
// .build()
//
// val request =
// sessionManager?.currentCastSession?.remoteMediaClient?.load(mediaInfo, options)
// request?.addStatusListener(this)
// }

// val remoteMediaClient: RemoteMediaClient?
// get() = sessionManager?.currentCastSession?.remoteMediaClient


// private fun createMediaInfo(mediaItem: MediaItem): MediaInfo {
// val metadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MUSIC_TRACK).apply {
// putString(MediaMetadata.KEY_TITLE, mediaItem.associatedMedia?.name ?: "Title")
// putString(MediaMetadata.KEY_ARTIST, mediaItem.associatedMedia?.author ?: "Artist")
// putString(
// MediaMetadata.KEY_ALBUM_TITLE,
// mediaItem.associatedMedia?.albumTitle ?: "Album"
// )
//
// mediaItem.associatedMedia?.coverUrl?.let {
// putString("images", it)
// }
// mediaItem.associatedMedia?.coverUrl?.let { coverUrl ->
// try {
// addImage(WebImage(Uri.parse(coverUrl.trim())))
// } catch (e: Exception) {
// Log.e(TAG, "Failed to add cover image: ${e.message}")
// }
// }
// }
//
// return MediaInfo.Builder(mediaItem.associatedMedia?.url!!)
// .setContentUrl(mediaItem.associatedMedia?.url!!)
// .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
// .setMetadata(metadata)
// .build()
// }

//CAST STATE LISTENER
override fun onCastStateChanged(state: Int) {
Log.d(
TAG,
"#NATIVE LOGS CAST ==> RECEIVER UPDATE AVAILABLE ${CastState.toString(state)}"
)

if (alreadyConnected && state == CastState.NOT_CONNECTED) {
alreadyConnected = false
}

if (!alreadyConnected) {
isConnected = state == CastState.CONNECTED
if (isConnected) {
alreadyConnected = true
onConnectCallback?.invoke()
}
}
}

//SessionAvailabilityListener
override fun onCastSessionAvailable() {
Log.d(TAG, "#NATIVE LOGS CAST ==>- SessionAvailabilityListener: onCastSessionAvailable")
}

override fun onCastSessionUnavailable() {
Log.d(TAG, "#NATIVE LOGS CAST ==>- SessionAvailabilityListener: onCastSessionUnavailable")
}

//PendingResult.StatusListener
override fun onComplete(status: Status) {
Log.d(TAG, "#NATIVE LOGS CAST ==> onComplete $status")
}


//SESSION MANAGER LISTENER
override fun onSessionEnded(p0: Session, p1: Int) {
Log.d(TAG, "#NATIVE LOGS CAST ==> onSessionEnded")
}

override fun onSessionEnding(p0: Session) {
Log.d(TAG, "#NATIVE LOGS CAST ==> onSessionEnding")
}

override fun onSessionResumeFailed(p0: Session, p1: Int) {
Log.d(TAG, "#NATIVE LOGS CAST ==> onSessionResumeFailed")
}

override fun onSessionResumed(p0: Session, p1: Boolean) {
Log.d(TAG, "#NATIVE LOGS CAST ==> onSessionResumed")
}

override fun onSessionResuming(p0: Session, p1: String) {
Log.d(TAG, "#NATIVE LOGS CAST ==> onSessionResuming")
}

override fun onSessionStartFailed(p0: Session, p1: Int) {
Log.d(TAG, "#NATIVE LOGS CAST ==> onSessionStartFailed $p0, $p1")
}

override fun onSessionStarted(p0: Session, p1: String) {
Log.d(TAG, "#NATIVE LOGS CAST ==> onCastSessionUnavailable")
onSessionEndedCallback?.invoke()
}

override fun onSessionStarting(p0: Session) {
Log.d(TAG, "#NATIVE LOGS CAST ==> $p0 onSessionStarting")
// OnePlayerSingleton.toggleCurrentPlayer(true)
}

override fun onSessionSuspended(p0: Session, p1: Int) {
Log.d(TAG, "#NATIVE LOGS CAST ==> onSessionSuspended")
}

// var MediaItem.associatedMedia: Media?
// get() = mediaItemMediaAssociations[this]
// set(value) {
// mediaItemMediaAssociations[this] = value
// }

fun setOnConnectCallback(callback: () -> Unit) {
onConnectCallback = callback
}

fun setOnSessionEndedCallback(callback: () -> Unit) {
onSessionEndedCallback = callback
}
}
Loading