mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-07-29 16:08:38 +02:00
Fix: check permission before media store query, Add: start of casting
This commit is contained in:
@@ -29,17 +29,22 @@
|
||||
android:name="com.google.android.gms.car.application"
|
||||
android:resource="@xml/automotive_app_desc"/>
|
||||
|
||||
<!-- Support for Cast -->
|
||||
<meta-data android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
|
||||
android:value="com.audiobookshelf.app.CastOptionsProvider"/>
|
||||
|
||||
<activity
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
|
||||
android:name="com.audiobookshelf.app.MainActivity"
|
||||
android:label="@string/title_activity_main"
|
||||
android:exported="true"
|
||||
android:theme="@style/AppTheme.NoActionBarLaunch"
|
||||
android:launchMode="singleTask">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.audiobookshelf.app
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.google.android.gms.cast.CastMediaControlIntent
|
||||
import com.google.android.gms.cast.framework.CastOptions
|
||||
import com.google.android.gms.cast.framework.OptionsProvider
|
||||
import com.google.android.gms.cast.framework.SessionProvider
|
||||
import com.google.android.gms.cast.framework.media.CastMediaOptions
|
||||
|
||||
class CastOptionsProvider : OptionsProvider {
|
||||
override fun getCastOptions(context: Context): CastOptions {
|
||||
Log.d("CastOptionsProvider", "getCastOptions")
|
||||
return CastOptions.Builder()
|
||||
.setReceiverApplicationId(CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID).setCastMediaOptions(
|
||||
CastMediaOptions.Builder()
|
||||
// We manage the media session and the notifications ourselves.
|
||||
.setMediaSessionEnabled(false)
|
||||
.setNotificationOptions(null)
|
||||
.build()
|
||||
)
|
||||
.setStopReceiverApplicationWhenEndingSession(true).build()
|
||||
}
|
||||
|
||||
override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,19 @@
|
||||
package com.audiobookshelf.app
|
||||
|
||||
import android.Manifest
|
||||
import android.content.ContentUris
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.MediaStore
|
||||
import android.support.v4.media.MediaMetadataCompat
|
||||
import android.util.Log
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.PermissionChecker.PERMISSION_GRANTED
|
||||
|
||||
class LocalMediaManager {
|
||||
private var ctx: Context
|
||||
@@ -40,8 +47,23 @@ class LocalMediaManager {
|
||||
fun loadLocalAudio() {
|
||||
Log.d(tag, "Media store looking for local audio files")
|
||||
|
||||
|
||||
if (ContextCompat.checkSelfPermission(ctx, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||
Log.e(tag, "Permission not granted to read from external storage")
|
||||
return
|
||||
}
|
||||
|
||||
val collection =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
MediaStore.Audio.Media.getContentUri(
|
||||
MediaStore.VOLUME_EXTERNAL
|
||||
)
|
||||
} else {
|
||||
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
|
||||
}
|
||||
|
||||
val proj = arrayOf(MediaStore.Audio.Media._ID, MediaStore.Audio.Media.DISPLAY_NAME, MediaStore.Audio.Media.DURATION, MediaStore.Audio.Media.SIZE)
|
||||
val audioCursor: Cursor? = ctx.contentResolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, proj, null, null, null)
|
||||
val audioCursor: Cursor? = ctx.contentResolver.query(collection, proj, null, null, null)
|
||||
|
||||
audioCursor?.use { cursor ->
|
||||
// Cache column indices.
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package com.audiobookshelf.app
|
||||
|
||||
import android.Manifest
|
||||
import android.app.DownloadManager
|
||||
import android.app.SearchManager
|
||||
import android.content.*
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.*
|
||||
import android.util.Log
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.anggrayudi.storage.SimpleStorage
|
||||
import com.anggrayudi.storage.SimpleStorageHelper
|
||||
import com.getcapacitor.BridgeActivity
|
||||
@@ -41,13 +45,6 @@ class MainActivity : BridgeActivity() {
|
||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// REMOVE FOR TESTING
|
||||
Log.d(tag, "STARTING UP APP")
|
||||
// var client: OkHttpClient = OkHttpClient()
|
||||
// var abManager = AudiobookManager(this, client)
|
||||
// abManager.init()
|
||||
// abManager.fetchAudiobooks()
|
||||
|
||||
Log.d(tag, "onCreate")
|
||||
registerPlugin(MyNativeAudio::class.java)
|
||||
registerPlugin(AudioDownloader::class.java)
|
||||
|
||||
@@ -231,4 +231,27 @@ class MyNativeAudio : Plugin() {
|
||||
playerNotificationService.cancelSleepTimer()
|
||||
call.resolve()
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun requestSession(call:PluginCall) {
|
||||
Log.d(tag, "CAST REQUEST SESSION PLUGIN")
|
||||
|
||||
playerNotificationService.requestSession(mainActivity, object : PlayerNotificationService.RequestSessionCallback() {
|
||||
override fun onError(errorCode: Int) {
|
||||
Log.e(tag, "CAST REQUEST SESSION CALLBACK ERROR $errorCode")
|
||||
}
|
||||
|
||||
override fun onCancel() {
|
||||
Log.d(tag, "CAST REQUEST SESSION ON CANCEL")
|
||||
}
|
||||
|
||||
override fun onJoin(jsonSession: JSONObject?) {
|
||||
Log.d(tag, "CAST REQUEST SESSION ON JOIN")
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,18 @@ import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.media.MediaBrowserServiceCompat
|
||||
import androidx.media.utils.MediaConstants
|
||||
import androidx.mediarouter.app.MediaRouteChooserDialog
|
||||
import androidx.mediarouter.media.MediaRouteSelector
|
||||
import androidx.mediarouter.media.MediaRouter
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.getcapacitor.JSObject
|
||||
import com.getcapacitor.PluginCall
|
||||
import com.google.android.exoplayer2.*
|
||||
import com.google.android.exoplayer2.audio.AudioAttributes
|
||||
import com.google.android.exoplayer2.ext.cast.CastPlayer
|
||||
import com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener
|
||||
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
|
||||
import com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator
|
||||
import com.google.android.exoplayer2.source.MediaSource
|
||||
@@ -33,8 +39,13 @@ import com.google.android.exoplayer2.source.ProgressiveMediaSource
|
||||
import com.google.android.exoplayer2.source.hls.HlsMediaSource
|
||||
import com.google.android.exoplayer2.ui.PlayerNotificationManager
|
||||
import com.google.android.exoplayer2.upstream.*
|
||||
import com.google.android.gms.cast.Cast.MessageReceivedCallback
|
||||
import com.google.android.gms.cast.CastDevice
|
||||
import com.google.android.gms.cast.CastMediaControlIntent
|
||||
import com.google.android.gms.cast.framework.*
|
||||
import kotlinx.coroutines.*
|
||||
import okhttp3.OkHttpClient
|
||||
import org.json.JSONObject
|
||||
import java.util.*
|
||||
import kotlin.concurrent.schedule
|
||||
|
||||
@@ -90,6 +101,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||
private var sleepChapterTime:Long = 0L
|
||||
|
||||
private lateinit var audiobookManager:AudiobookManager
|
||||
private var newConnectionListener:SessionListener? = null
|
||||
|
||||
fun setCustomObjectListener(mylistener: MyCustomObjectListener) {
|
||||
listener = mylistener
|
||||
@@ -490,9 +502,6 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||
terminateStream()
|
||||
}
|
||||
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> {
|
||||
Log.d(tag, "PLAY PAUSE TEST")
|
||||
// transportControls.playFromSearch("Brave New World", Bundle())
|
||||
|
||||
if (mPlayer.isPlaying) {
|
||||
if (0 == mediaButtonClickCount) pause()
|
||||
handleMediaButtonClickCount()
|
||||
@@ -997,5 +1006,361 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||
sleepTimerTask = null
|
||||
sleepChapterTime = 0L
|
||||
}
|
||||
|
||||
/**
|
||||
* If Cast is available, create a CastPlayer to handle communication with a Cast session.
|
||||
*/
|
||||
private val castPlayer: CastPlayer? by lazy {
|
||||
try {
|
||||
val castContext = CastContext.getSharedInstance(this)
|
||||
CastPlayer(castContext).apply {
|
||||
setSessionAvailabilityListener(CastSessionAvailabilityListener())
|
||||
// addListener(playerListener)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// We wouldn't normally catch the generic `Exception` however
|
||||
// calling `CastContext.getSharedInstance` can throw various exceptions, all of which
|
||||
// indicate that Cast is unavailable.
|
||||
// Related internal bug b/68009560.
|
||||
Log.i(tag, "Cast is not available on this device. " +
|
||||
"Exception thrown when attempting to obtain CastContext. " + e.message)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private inner class CastSessionAvailabilityListener : SessionAvailabilityListener {
|
||||
|
||||
/**
|
||||
* Called when a Cast session has started and the user wishes to control playback on a
|
||||
* remote Cast receiver rather than play audio locally.
|
||||
*/
|
||||
override fun onCastSessionAvailable() {
|
||||
// switchToPlayer(currentPlayer, castPlayer!!)
|
||||
Log.d(tag, "CAST SeSSION AVAILABLE " + castPlayer?.deviceInfo)
|
||||
mediaSessionConnector.setPlayer(castPlayer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a Cast session has ended and the user wishes to control playback locally.
|
||||
*/
|
||||
override fun onCastSessionUnavailable() {
|
||||
// switchToPlayer(currentPlayer, exoPlayer)
|
||||
Log.d(tag, "CAST SESSION UNAVAILABLE")
|
||||
mediaSessionConnector.setPlayer(mPlayer)
|
||||
}
|
||||
}
|
||||
|
||||
fun requestSession(mainActivity:Activity, callback: RequestSessionCallback) {
|
||||
mainActivity.runOnUiThread(object: Runnable {
|
||||
override fun run() {
|
||||
Log.d(tag, "CAST RUNNING ON MAIN THREAD")
|
||||
|
||||
val session: CastSession? = getSession()
|
||||
if (session == null) {
|
||||
// show the "choose a connection" dialog
|
||||
|
||||
// Add the connection listener callback
|
||||
listenForConnection(callback)
|
||||
|
||||
// Create the dialog
|
||||
// TODO accept theme as a config.xml option
|
||||
val builder = MediaRouteChooserDialog(mainActivity, androidx.appcompat.R.style.Theme_AppCompat_NoActionBar)
|
||||
builder.routeSelector = MediaRouteSelector.Builder()
|
||||
.addControlCategory(CastMediaControlIntent.categoryForCast(CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID))
|
||||
.build()
|
||||
builder.setCanceledOnTouchOutside(true)
|
||||
builder.setOnCancelListener {
|
||||
getSessionManager()!!.removeSessionManagerListener(newConnectionListener, CastSession::class.java)
|
||||
callback.onCancel()
|
||||
}
|
||||
builder.show()
|
||||
} else {
|
||||
// We are are already connected, so show the "connection options" Dialog
|
||||
val builder: AlertDialog.Builder = AlertDialog.Builder(mainActivity)
|
||||
if (session.castDevice != null) {
|
||||
builder.setTitle(session.castDevice.friendlyName)
|
||||
}
|
||||
builder.setOnDismissListener { callback.onCancel() }
|
||||
builder.setPositiveButton("Stop Casting") { dialog, which -> endSession(true, null) }
|
||||
builder.show()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
abstract class RequestSessionCallback : ConnectionCallback {
|
||||
abstract fun onError(errorCode: Int)
|
||||
abstract fun onCancel()
|
||||
override fun onSessionEndedBeforeStart(errorCode: Int): Boolean {
|
||||
onSessionStartFailed(errorCode)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onSessionStartFailed(errorCode: Int): Boolean {
|
||||
onError(errorCode)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
fun endSession(stopCasting: Boolean, pluginCall: PluginCall?) {
|
||||
|
||||
getSessionManager()!!.addSessionManagerListener(object : SessionListener() {
|
||||
override fun onSessionEnded(castSession: CastSession?, error: Int) {
|
||||
getSessionManager()!!.removeSessionManagerListener(this, CastSession::class.java)
|
||||
Log.d(tag, "CAST END SESSION")
|
||||
// media.setSession(null)
|
||||
pluginCall?.resolve()
|
||||
// listener.onSessionEnd(ChromecastUtilities.createSessionObject(castSession, if (stopCasting) "stopped" else "disconnected"))
|
||||
}
|
||||
}, CastSession::class.java)
|
||||
getSessionManager()!!.endCurrentSession(stopCasting)
|
||||
|
||||
}
|
||||
|
||||
open class SessionListener : SessionManagerListener<CastSession> {
|
||||
override fun onSessionStarting(castSession: CastSession?) {}
|
||||
override fun onSessionStarted(castSession: CastSession?, sessionId: String) {}
|
||||
override fun onSessionStartFailed(castSession: CastSession?, error: Int) {}
|
||||
override fun onSessionEnding(castSession: CastSession?) {}
|
||||
override fun onSessionEnded(castSession: CastSession?, error: Int) {}
|
||||
override fun onSessionResuming(castSession: CastSession?, sessionId: String) {}
|
||||
override fun onSessionResumed(castSession: CastSession?, wasSuspended: Boolean) {}
|
||||
override fun onSessionResumeFailed(castSession: CastSession?, error: Int) {}
|
||||
override fun onSessionSuspended(castSession: CastSession?, reason: Int) {}
|
||||
}
|
||||
|
||||
private fun startRouteScan() {
|
||||
var connListener = object: ChromecastListener() {
|
||||
override fun onReceiverAvailableUpdate(available: Boolean) {
|
||||
Log.d(tag, "CAST RECEIVER UPDATE AVAILABLE $available")
|
||||
}
|
||||
|
||||
override fun onSessionRejoin(jsonSession: JSONObject?) {
|
||||
Log.d(tag, "CAST onSessionRejoin")
|
||||
}
|
||||
|
||||
override fun onMediaLoaded(jsonMedia: JSONObject?) {
|
||||
Log.d(tag, "CAST onMediaLoaded")
|
||||
}
|
||||
|
||||
override fun onMediaUpdate(jsonMedia: JSONObject?) {
|
||||
Log.d(tag, "CAST onMediaUpdate")
|
||||
}
|
||||
|
||||
override fun onSessionUpdate(jsonSession: JSONObject?) {
|
||||
Log.d(tag, "CAST onSessionUpdate")
|
||||
}
|
||||
|
||||
override fun onSessionEnd(jsonSession: JSONObject?) {
|
||||
Log.d(tag, "CAST onSessionEnd")
|
||||
}
|
||||
|
||||
override fun onMessageReceived(p0: CastDevice, p1: String, p2: String) {
|
||||
Log.d(tag, "CAST onMessageReceived")
|
||||
}
|
||||
}
|
||||
|
||||
var callback = object : ScanCallback() {
|
||||
override fun onRouteUpdate(routes: List<MediaRouter.RouteInfo>?) {
|
||||
Log.d(tag, "CAST On ROUTE UPDATED ${routes?.size} | ${getContext().castState}")
|
||||
// if the routes have changed, we may have an available device
|
||||
// If there is at least one device available
|
||||
if (getContext().castState != CastState.NO_DEVICES_AVAILABLE) {
|
||||
|
||||
routes?.forEach { Log.d(tag, "CAST ROUTE ${it.description} | ${it.deviceType} | ${it.isBluetooth} | ${it.name}") }
|
||||
|
||||
// Stop the scan
|
||||
stopRouteScan(this, null);
|
||||
// Let the client know a receiver is available
|
||||
connListener.onReceiverAvailableUpdate(true);
|
||||
// Since we have a receiver we may also have an active session
|
||||
var session = getSessionManager()?.currentCastSession;
|
||||
// If we do have a session
|
||||
if (session != null) {
|
||||
// Let the client know
|
||||
Log.d(tag, "LET SESSION KNOW ABOUT")
|
||||
// media.setSession(session);
|
||||
// connListener.onSessionRejoin(ChromecastUtilities.createSessionObject(session));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
callback.setMediaRouter(getMediaRouter())
|
||||
|
||||
callback.onFilteredRouteUpdate();
|
||||
|
||||
getMediaRouter()!!.addCallback(MediaRouteSelector.Builder()
|
||||
.addControlCategory(CastMediaControlIntent.categoryForCast(CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID))
|
||||
.build(),
|
||||
callback,
|
||||
MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN)
|
||||
}
|
||||
|
||||
internal interface CastListener : MessageReceivedCallback {
|
||||
fun onMediaLoaded(jsonMedia: JSONObject?)
|
||||
fun onMediaUpdate(jsonMedia: JSONObject?)
|
||||
fun onSessionUpdate(jsonSession: JSONObject?)
|
||||
fun onSessionEnd(jsonSession: JSONObject?)
|
||||
}
|
||||
|
||||
internal abstract class ChromecastListener : CastStateListener, CastListener {
|
||||
abstract fun onReceiverAvailableUpdate(available: Boolean)
|
||||
abstract fun onSessionRejoin(jsonSession: JSONObject?)
|
||||
|
||||
/** CastStateListener functions. */
|
||||
override fun onCastStateChanged(state: Int) {
|
||||
onReceiverAvailableUpdate(state != CastState.NO_DEVICES_AVAILABLE)
|
||||
}
|
||||
}
|
||||
|
||||
fun stopRouteScan(callback: ScanCallback?, completionCallback: Runnable?) {
|
||||
if (callback == null) {
|
||||
completionCallback!!.run()
|
||||
return
|
||||
}
|
||||
// ctx.runOnUiThread(Runnable {
|
||||
callback.stop()
|
||||
getMediaRouter()!!.removeCallback(callback)
|
||||
completionCallback?.run()
|
||||
// })
|
||||
}
|
||||
|
||||
abstract class ScanCallback : MediaRouter.Callback() {
|
||||
/**
|
||||
* Called whenever a route is updated.
|
||||
* @param routes the currently available routes
|
||||
*/
|
||||
abstract fun onRouteUpdate(routes: List<MediaRouter.RouteInfo>?)
|
||||
|
||||
/** records whether we have been stopped or not. */
|
||||
private var stopped = false
|
||||
|
||||
/** Global mediaRouter object. */
|
||||
private var mediaRouter: MediaRouter? = null
|
||||
|
||||
/**
|
||||
* Sets the mediaRouter object.
|
||||
* @param router mediaRouter object
|
||||
*/
|
||||
fun setMediaRouter(router: MediaRouter?) {
|
||||
mediaRouter = router
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this method when you wish to stop scanning.
|
||||
* It is important that it is called, otherwise battery
|
||||
* life will drain more quickly.
|
||||
*/
|
||||
fun stop() {
|
||||
stopped = true
|
||||
}
|
||||
|
||||
fun onFilteredRouteUpdate() {
|
||||
if (stopped || mediaRouter == null) {
|
||||
return
|
||||
}
|
||||
val outRoutes: MutableList<MediaRouter.RouteInfo> = ArrayList()
|
||||
// Filter the routes
|
||||
for (route in mediaRouter!!.routes) {
|
||||
// We don't want default routes, or duplicate active routes
|
||||
// or multizone duplicates https://github.com/jellyfin/cordova-plugin-chromecast/issues/32
|
||||
val extras: Bundle? = route.extras
|
||||
if (extras != null) {
|
||||
CastDevice.getFromBundle(extras)
|
||||
if (extras.getString("com.google.android.gms.cast.EXTRA_SESSION_ID") != null) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if (!route.isDefault
|
||||
&& !route.description.equals("Google Cast Multizone Member")
|
||||
&& route.playbackType === MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE) {
|
||||
outRoutes.add(route)
|
||||
}
|
||||
}
|
||||
onRouteUpdate(outRoutes)
|
||||
}
|
||||
|
||||
override fun onRouteAdded(router: MediaRouter?, route: MediaRouter.RouteInfo?) {
|
||||
onFilteredRouteUpdate()
|
||||
}
|
||||
|
||||
override fun onRouteChanged(router: MediaRouter?, route: MediaRouter.RouteInfo?) {
|
||||
onFilteredRouteUpdate()
|
||||
}
|
||||
|
||||
override fun onRouteRemoved(router: MediaRouter?, route: MediaRouter.RouteInfo?) {
|
||||
onFilteredRouteUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
private fun listenForConnection(callback: ConnectionCallback) {
|
||||
// We should only ever have one of these listeners active at a time, so remove previous
|
||||
getSessionManager()?.removeSessionManagerListener(newConnectionListener, CastSession::class.java)
|
||||
|
||||
newConnectionListener = object : SessionListener() {
|
||||
override fun onSessionStarted(castSession: CastSession?, sessionId: String) {
|
||||
Log.d(tag, "CAST SESSION STARTED ${castSession?.castDevice?.friendlyName}")
|
||||
getSessionManager()?.removeSessionManagerListener(this, CastSession::class.java)
|
||||
// media.setSession(castSession)
|
||||
// callback.onJoin(ChromecastUtilities.createSessionObject(castSession))
|
||||
}
|
||||
|
||||
override fun onSessionStartFailed(castSession: CastSession?, errCode: Int) {
|
||||
if (callback.onSessionStartFailed(errCode)) {
|
||||
getSessionManager()?.removeSessionManagerListener(this, CastSession::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSessionEnded(castSession: CastSession?, errCode: Int) {
|
||||
if (callback.onSessionEndedBeforeStart(errCode)) {
|
||||
getSessionManager()?.removeSessionManagerListener(this, CastSession::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getSessionManager()?.addSessionManagerListener(newConnectionListener, CastSession::class.java)
|
||||
}
|
||||
|
||||
private fun getContext(): CastContext {
|
||||
return CastContext.getSharedInstance(ctx)
|
||||
}
|
||||
|
||||
private fun getSessionManager(): SessionManager? {
|
||||
return getContext().sessionManager
|
||||
}
|
||||
|
||||
private fun getMediaRouter(): MediaRouter? {
|
||||
return MediaRouter.getInstance(ctx)
|
||||
}
|
||||
|
||||
private fun getSession(): CastSession? {
|
||||
return getSessionManager()?.currentCastSession
|
||||
}
|
||||
|
||||
internal interface ConnectionCallback {
|
||||
/**
|
||||
* Successfully joined a session on a route.
|
||||
* @param jsonSession the session we joined
|
||||
*/
|
||||
fun onJoin(jsonSession: JSONObject?)
|
||||
|
||||
/**
|
||||
* Called if we received an error.
|
||||
* @param errorCode You can find the error meaning here:
|
||||
* https://developers.google.com/android/reference/com/google/android/gms/cast/CastStatusCodes
|
||||
* @return true if we are done listening for join, false, if we to keep listening
|
||||
*/
|
||||
fun onSessionStartFailed(errorCode: Int): Boolean
|
||||
|
||||
/**
|
||||
* Called when we detect a session ended event before session started.
|
||||
* See issues:
|
||||
* https://github.com/jellyfin/cordova-plugin-chromecast/issues/49
|
||||
* https://github.com/jellyfin/cordova-plugin-chromecast/issues/48
|
||||
* @param errorCode error to output
|
||||
* @return true if we are done listening for join, false, if we to keep listening
|
||||
*/
|
||||
fun onSessionEndedBeforeStart(errorCode: Int): Boolean
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,10 +11,7 @@ import com.anggrayudi.storage.SimpleStorage
|
||||
import com.anggrayudi.storage.callback.FolderPickerCallback
|
||||
import com.anggrayudi.storage.callback.StorageAccessCallback
|
||||
import com.anggrayudi.storage.file.*
|
||||
import com.getcapacitor.JSObject
|
||||
import com.getcapacitor.Plugin
|
||||
import com.getcapacitor.PluginCall
|
||||
import com.getcapacitor.PluginMethod
|
||||
import com.getcapacitor.*
|
||||
import com.getcapacitor.annotation.CapacitorPlugin
|
||||
|
||||
@CapacitorPlugin(name = "StorageManager")
|
||||
@@ -160,7 +157,17 @@ class StorageManager : Plugin() {
|
||||
var folderUrl = call.data.getString("folderUrl", "").toString()
|
||||
Log.d(TAG, "Searching folder $folderUrl")
|
||||
|
||||
var df: DocumentFile = DocumentFileCompat.fromUri(context, Uri.parse(folderUrl))!!
|
||||
var df: DocumentFile? = DocumentFileCompat.fromUri(context, Uri.parse(folderUrl))
|
||||
|
||||
if (df == null) {
|
||||
Log.e(TAG, "Folder Doc File Invalid $folderUrl")
|
||||
var jsobj = JSObject()
|
||||
jsobj.put("folders", JSArray())
|
||||
jsobj.put("files", JSArray())
|
||||
call.resolve(jsobj)
|
||||
return
|
||||
}
|
||||
|
||||
Log.d(TAG, "Folder as DF ${df.isDirectory} | ${df.getSimplePath(context)} | ${df.getBasePath(context)} | ${df.name}")
|
||||
|
||||
var mediaFolders = mutableListOf<MediaFolder>()
|
||||
|
||||
Reference in New Issue
Block a user