mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-07-29 07:58:43 +02:00
New data model classes, ffmpeg-kit, jackson json deserializer, add permission
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
<!-- Permissions -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.audiobookshelf.app
|
||||
|
||||
import android.Manifest
|
||||
import android.app.DownloadManager
|
||||
import android.content.*
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.*
|
||||
import android.util.Log
|
||||
import androidx.core.app.ActivityCompat
|
||||
import com.anggrayudi.storage.SimpleStorage
|
||||
import com.anggrayudi.storage.SimpleStorageHelper
|
||||
import com.audiobookshelf.app.data.DbManager
|
||||
@@ -24,6 +27,11 @@ class MainActivity : BridgeActivity() {
|
||||
val storageHelper = SimpleStorageHelper(this)
|
||||
val storage = SimpleStorage(this)
|
||||
|
||||
val REQUEST_PERMISSIONS = 1
|
||||
var PERMISSIONS_ALL = arrayOf(
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||
)
|
||||
|
||||
val broadcastReceiver = object: BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
when (intent?.action) {
|
||||
@@ -43,6 +51,14 @@ class MainActivity : BridgeActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
Log.d(tag, "onCreate")
|
||||
|
||||
var permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
if (permission != PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(this,
|
||||
PERMISSIONS_ALL,
|
||||
REQUEST_PERMISSIONS)
|
||||
}
|
||||
|
||||
registerPlugin(MyNativeAudio::class.java)
|
||||
registerPlugin(AudioDownloader::class.java)
|
||||
registerPlugin(StorageManager::class.java)
|
||||
|
||||
@@ -67,12 +67,13 @@ class MyNativeAudio : Plugin() {
|
||||
fun prepareLibraryItem(call: PluginCall) {
|
||||
var libraryItemId = call.getString("libraryItemId", "").toString()
|
||||
var mediaEntityId = call.getString("mediaEntityId", "").toString()
|
||||
var playWhenReady = call.getBoolean("playWhenReady") == true
|
||||
|
||||
apiHandler.playLibraryItem(libraryItemId) {
|
||||
|
||||
Handler(Looper.getMainLooper()).post() {
|
||||
Log.d(tag, "Preparing Player TEST ${jacksonObjectMapper().writeValueAsString(it)}")
|
||||
playerNotificationService.preparePlayer(it)
|
||||
playerNotificationService.preparePlayer(it, playWhenReady)
|
||||
}
|
||||
|
||||
call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(it)))
|
||||
|
||||
@@ -381,8 +381,8 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||
.setMediaId(currentPlaybackSession!!.id)
|
||||
.setTitle(currentPlaybackSession!!.getTitle())
|
||||
.setSubtitle(currentPlaybackSession!!.getAuthor())
|
||||
// .setMediaUri(currentPlaybackSession!!.getContentUri())
|
||||
// .setIconUri(currentAudiobookStreamData!!.)
|
||||
.setMediaUri(currentPlaybackSession!!.getContentUri())
|
||||
.setIconUri(currentPlaybackSession!!.getCoverUri())
|
||||
return builder.build()
|
||||
}
|
||||
}
|
||||
@@ -666,21 +666,44 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||
/*
|
||||
User callable methods
|
||||
*/
|
||||
fun preparePlayer(playbackSession: PlaybackSession) {
|
||||
fun preparePlayer(playbackSession: PlaybackSession, playWhenReady:Boolean) {
|
||||
currentPlaybackSession = playbackSession
|
||||
var metadata = playbackSession.getMediaMetadataCompat()
|
||||
mediaSession.setMetadata(metadata)
|
||||
var mediaMetadata = playbackSession.getMediaMetadata()
|
||||
var mediaUrl = playbackSession.getContentUri()
|
||||
var mimeType = playbackSession.getMimeType()
|
||||
Log.d(tag, "Media URL $mediaUrl")
|
||||
var mediaUri = Uri.parse(mediaUrl)
|
||||
var mediaItem = MediaItem.Builder().setUri(mediaUri).setMediaMetadata(mediaMetadata).setMimeType(mimeType).build()
|
||||
var dataSourceFactory = DefaultDataSourceFactory(ctx, channelId)
|
||||
var mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem)
|
||||
mPlayer.setMediaSource(mediaSource, 0L)
|
||||
mPlayer.prepare()
|
||||
mPlayer.playWhenReady = true
|
||||
var mediaMetadata = playbackSession.getExoMediaMetadata()
|
||||
|
||||
|
||||
// var mediaUri = playbackSession.getContentUri()
|
||||
// var mimeType = playbackSession.getMimeType()
|
||||
// var mediaItem = MediaItem.Builder().setUri(mediaUri).setMediaMetadata(mediaMetadata).setMimeType(mimeType).build()
|
||||
// var dataSourceFactory = DefaultDataSourceFactory(ctx, channelId)
|
||||
// var mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem)
|
||||
// mPlayer.setMediaSource(mediaSource, 0L)
|
||||
|
||||
// if (mPlayer == currentPlayer) {
|
||||
// var mediaSource:MediaSource
|
||||
//
|
||||
// if (currentAudiobookStreamData!!.isLocal) {
|
||||
// Log.d(tag, "Playing Local File")
|
||||
// var dataSourceFactory = DefaultDataSourceFactory(ctx, channelId)
|
||||
// mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem)
|
||||
// } else {
|
||||
// Log.d(tag, "Playing HLS File")
|
||||
// var dataSourceFactory = DefaultHttpDataSource.Factory()
|
||||
// dataSourceFactory.setUserAgent(channelId)
|
||||
// dataSourceFactory.setDefaultRequestProperties(hashMapOf("Authorization" to "Bearer ${currentAudiobookStreamData!!.token}"))
|
||||
// mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem)
|
||||
// }
|
||||
// mPlayer.setMediaSource(mediaSource, currentAudiobookStreamData!!.startTime)
|
||||
// } else if (castPlayer != null) {
|
||||
//// var mediaQueue = currentAudiobookStreamData!!.getCastQueue()
|
||||
// // TODO: Start position will need to be adjusted if using multi-track queue
|
||||
//// castPlayer?.setMediaItems(mediaQueue, 0, 0)
|
||||
// }
|
||||
|
||||
currentPlayer.prepare()
|
||||
currentPlayer.playWhenReady = playWhenReady
|
||||
currentPlayer.setPlaybackSpeed(1f) // TODO: Playback speed should come from settings
|
||||
}
|
||||
|
||||
fun initPlayer(audiobookStreamData: AudiobookStreamData) {
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
package com.audiobookshelf.app
|
||||
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
|
||||
import com.anggrayudi.storage.SimpleStorage
|
||||
import com.anggrayudi.storage.callback.FolderPickerCallback
|
||||
import com.anggrayudi.storage.callback.StorageAccessCallback
|
||||
import com.anggrayudi.storage.file.*
|
||||
import com.audiobookshelf.app.device.FolderScanner
|
||||
import com.getcapacitor.*
|
||||
import com.getcapacitor.annotation.CapacitorPlugin
|
||||
|
||||
@@ -112,6 +116,7 @@ class StorageManager : Plugin() {
|
||||
call.resolve(jsobj)
|
||||
}
|
||||
}
|
||||
|
||||
mainActivity.storage.openFolderPicker(6)
|
||||
}
|
||||
|
||||
@@ -126,12 +131,11 @@ class StorageManager : Plugin() {
|
||||
@PluginMethod
|
||||
fun checkStoragePermission(call: PluginCall) {
|
||||
var res = false
|
||||
|
||||
if (Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.P) {
|
||||
res = SimpleStorage.hasStoragePermission(context)
|
||||
Log.d(TAG, "Check Storage Access $res")
|
||||
Log.d(TAG, "checkStoragePermission: Check Storage Access $res")
|
||||
} else {
|
||||
Log.d(TAG, "Has permission on Android 10 or up")
|
||||
Log.d(TAG, "checkStoragePermission: Has permission on Android 10 or up")
|
||||
res = true
|
||||
}
|
||||
|
||||
@@ -157,58 +161,63 @@ 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))
|
||||
|
||||
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>()
|
||||
var foldersFound = df.search(false, DocumentFileType.FOLDER)
|
||||
|
||||
foldersFound.forEach {
|
||||
Log.d(TAG, "Iterating over Folder Found ${it.name} | ${it.getSimplePath(context)} | URI: ${it.uri}")
|
||||
var folderName = it.name ?: ""
|
||||
var mediaFiles = mutableListOf<MediaFile>()
|
||||
|
||||
var filesInFolder = it.search(false, DocumentFileType.FILE, arrayOf("audio/*", "image/*"))
|
||||
filesInFolder.forEach { it2 ->
|
||||
var mimeType = it2?.mimeType ?: ""
|
||||
var filename = it2?.name ?: ""
|
||||
var isAudio = mimeType.startsWith("audio")
|
||||
Log.d(TAG, "Found $mimeType file $filename in folder $folderName")
|
||||
var imageFile = MediaFile(it2.uri, filename, it2.getSimplePath(context), it2.length(), mimeType, isAudio)
|
||||
mediaFiles.add(imageFile)
|
||||
}
|
||||
if (mediaFiles.size > 0) {
|
||||
mediaFolders.add(MediaFolder(it.uri, folderName, it.getSimplePath(context), mediaFiles))
|
||||
}
|
||||
}
|
||||
|
||||
// Files in root dir
|
||||
var rootMediaFiles = mutableListOf<MediaFile>()
|
||||
var mediaFilesFound:List<DocumentFile> = df.search(false, DocumentFileType.FILE, arrayOf("audio/*", "image/*"))
|
||||
mediaFilesFound.forEach {
|
||||
Log.d(TAG, "Folder Root File Found ${it.name} | ${it.getSimplePath(context)} | URI: ${it.uri} | ${it.mimeType}")
|
||||
var mimeType = it?.mimeType ?: ""
|
||||
var filename = it?.name ?: ""
|
||||
var isAudio = mimeType.startsWith("audio")
|
||||
Log.d(TAG, "Found $mimeType file $filename in root folder")
|
||||
var imageFile = MediaFile(it.uri, filename, it.getSimplePath(context), it.length(), mimeType, isAudio)
|
||||
rootMediaFiles.add(imageFile)
|
||||
}
|
||||
|
||||
var jsobj = JSObject()
|
||||
jsobj.put("folders", mediaFolders.map{ it.toJSObject() })
|
||||
jsobj.put("files", rootMediaFiles.map{ it.toJSObject() })
|
||||
call.resolve(jsobj)
|
||||
var folderScanner = FolderScanner(context)
|
||||
var data = folderScanner.scanForAudiobooks(folderUrl)
|
||||
Log.d(TAG, "Scan DATA $data")
|
||||
call.resolve(JSObject())
|
||||
//
|
||||
// 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>()
|
||||
// var foldersFound = df.search(false, DocumentFileType.FOLDER)
|
||||
//
|
||||
// foldersFound.forEach {
|
||||
// Log.d(TAG, "Iterating over Folder Found ${it.name} | ${it.getSimplePath(context)} | URI: ${it.uri}")
|
||||
// var folderName = it.name ?: ""
|
||||
// var mediaFiles = mutableListOf<MediaFile>()
|
||||
//
|
||||
// var filesInFolder = it.search(false, DocumentFileType.FILE, arrayOf("audio/*", "image/*"))
|
||||
// filesInFolder.forEach { it2 ->
|
||||
// var mimeType = it2?.mimeType ?: ""
|
||||
// var filename = it2?.name ?: ""
|
||||
// var isAudio = mimeType.startsWith("audio")
|
||||
// Log.d(TAG, "Found $mimeType file $filename in folder $folderName")
|
||||
// var imageFile = MediaFile(it2.uri, filename, it2.getSimplePath(context), it2.length(), mimeType, isAudio)
|
||||
// mediaFiles.add(imageFile)
|
||||
// }
|
||||
// if (mediaFiles.size > 0) {
|
||||
// mediaFolders.add(MediaFolder(it.uri, folderName, it.getSimplePath(context), mediaFiles))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Files in root dir
|
||||
// var rootMediaFiles = mutableListOf<MediaFile>()
|
||||
// var mediaFilesFound:List<DocumentFile> = df.search(false, DocumentFileType.FILE, arrayOf("audio/*", "image/*"))
|
||||
// mediaFilesFound.forEach {
|
||||
// Log.d(TAG, "Folder Root File Found ${it.name} | ${it.getSimplePath(context)} | URI: ${it.uri} | ${it.mimeType}")
|
||||
// var mimeType = it?.mimeType ?: ""
|
||||
// var filename = it?.name ?: ""
|
||||
// var isAudio = mimeType.startsWith("audio")
|
||||
// Log.d(TAG, "Found $mimeType file $filename in root folder")
|
||||
// var imageFile = MediaFile(it.uri, filename, it.getSimplePath(context), it.length(), mimeType, isAudio)
|
||||
// rootMediaFiles.add(imageFile)
|
||||
// }
|
||||
//
|
||||
// var jsobj = JSObject()
|
||||
// jsobj.put("folders", mediaFolders.map{ it.toJSObject() })
|
||||
// jsobj.put("files", rootMediaFiles.map{ it.toJSObject() })
|
||||
// call.resolve(jsobj)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.audiobookshelf.app.data
|
||||
|
||||
import android.net.Uri
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class AudioProbeStream(
|
||||
val index:Int,
|
||||
val codec_name:String,
|
||||
val codec_long_name:String,
|
||||
val channels:Int,
|
||||
val channel_layout:String,
|
||||
val duration:Double,
|
||||
val bit_rate:Double
|
||||
)
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class AudioProbeChapterTags(
|
||||
val title:String
|
||||
)
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class AudioProbeChapter(
|
||||
val id:Int,
|
||||
val start:Int,
|
||||
val end:Int,
|
||||
val tags:AudioProbeChapterTags
|
||||
)
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class AudioProbeFormatTags(
|
||||
val artist:String?,
|
||||
val album:String?,
|
||||
val comment:String?,
|
||||
val date:String?,
|
||||
val genre:String?,
|
||||
val title:String?
|
||||
)
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class AudioProbeFormat(
|
||||
val filename:String,
|
||||
val format_name:String,
|
||||
val duration:Double,
|
||||
val size:Long,
|
||||
val bit_rate:Double,
|
||||
val tags:AudioProbeFormatTags
|
||||
)
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
class AudioProbeResult (
|
||||
val streams:MutableList<AudioProbeStream>,
|
||||
val chapters:MutableList<AudioProbeChapter>,
|
||||
val format:AudioProbeFormat) {
|
||||
|
||||
val duration get() = format.duration
|
||||
val size get() = format.size
|
||||
val title get() = format.tags.title ?: format.filename.split("/").last()
|
||||
val artist get() = format.tags.artist ?: ""
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
package com.audiobookshelf.app.data
|
||||
|
||||
import android.net.Uri
|
||||
import android.support.v4.media.MediaMetadataCompat
|
||||
import com.fasterxml.jackson.annotation.*
|
||||
import com.google.android.exoplayer2.MediaMetadata
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class LibraryItem(
|
||||
@@ -23,7 +20,7 @@ data class LibraryItem(
|
||||
var isMissing:Boolean,
|
||||
var isInvalid:Boolean,
|
||||
var mediaType:String,
|
||||
var media:MediaEntity,
|
||||
var media:MediaType,
|
||||
var libraryFiles:MutableList<LibraryFile>
|
||||
)
|
||||
|
||||
@@ -33,7 +30,7 @@ data class LibraryItem(
|
||||
JsonSubTypes.Type(Book::class),
|
||||
JsonSubTypes.Type(Podcast::class)
|
||||
)
|
||||
open class MediaEntity {}
|
||||
open class MediaType {}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class Podcast(
|
||||
@@ -42,15 +39,15 @@ data class Podcast(
|
||||
var tags:MutableList<String>,
|
||||
var episodes:MutableList<PodcastEpisode>,
|
||||
var autoDownloadEpisodes:Boolean
|
||||
) : MediaEntity()
|
||||
) : MediaType()
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class Book(
|
||||
var metadata:BookMetadata,
|
||||
var coverPath:String?,
|
||||
var tags:MutableList<String>,
|
||||
var audiobooks:MutableList<Audiobook>
|
||||
) : MediaEntity()
|
||||
var audioFiles:MutableList<AudioFile>
|
||||
) : MediaType()
|
||||
|
||||
// This auto-detects whether it is a Book or Podcast
|
||||
@JsonTypeInfo(use=JsonTypeInfo.Id.DEDUCTION)
|
||||
@@ -58,14 +55,29 @@ data class Book(
|
||||
JsonSubTypes.Type(BookMetadata::class),
|
||||
JsonSubTypes.Type(PodcastMetadata::class)
|
||||
)
|
||||
open class MediaEntityMetadata {}
|
||||
open class MediaTypeMetadata {}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class BookMetadata(
|
||||
var title:String,
|
||||
var subtitle:String?,
|
||||
var authors:MutableList<Author>
|
||||
) : MediaEntityMetadata()
|
||||
var authors:MutableList<Author>,
|
||||
var narrators:MutableList<String>,
|
||||
var genres:MutableList<String>,
|
||||
var publishedYear:String?,
|
||||
var publishedDate:String?,
|
||||
var publisher:String?,
|
||||
var description:String?,
|
||||
var isbn:String?,
|
||||
var asin:String?,
|
||||
var language:String?,
|
||||
var explicit:Boolean,
|
||||
// In toJSONExpanded
|
||||
var authorName:String?,
|
||||
var authorNameLF:String?,
|
||||
var narratorName:String?,
|
||||
var seriesName:String?
|
||||
) : MediaTypeMetadata()
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class PodcastMetadata(
|
||||
@@ -73,7 +85,7 @@ data class PodcastMetadata(
|
||||
var author:String?,
|
||||
var feedUrl:String,
|
||||
var genres:MutableList<String>
|
||||
) : MediaEntityMetadata()
|
||||
) : MediaTypeMetadata()
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class Author(
|
||||
@@ -82,14 +94,6 @@ data class Author(
|
||||
var coverPath:String?
|
||||
)
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class Audiobook(
|
||||
var id:String,
|
||||
var index:Int,
|
||||
var name:String,
|
||||
var audioFiles:MutableList<AudioFile>
|
||||
)
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class PodcastEpisode(
|
||||
var id:String,
|
||||
@@ -138,68 +142,6 @@ data class Folder(
|
||||
var fullPath:String
|
||||
)
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
class PlaybackSession(
|
||||
var id:String,
|
||||
var userId:String,
|
||||
var libraryItemId:String,
|
||||
var mediaEntityId:String,
|
||||
var mediaType:String,
|
||||
var mediaMetadata:MediaEntityMetadata,
|
||||
var duration:Double,
|
||||
var playMethod:Int,
|
||||
var audioTracks:MutableList<AudioTrack>,
|
||||
var currentTime:Double,
|
||||
var serverUrl:String,
|
||||
var token:String
|
||||
) {
|
||||
fun getTitle():String {
|
||||
var metadata = mediaMetadata as BookMetadata
|
||||
return metadata.title
|
||||
}
|
||||
fun getAuthor():String {
|
||||
var metadata = mediaMetadata as BookMetadata
|
||||
return metadata.authors.joinToString(",") { it.name }
|
||||
}
|
||||
fun getContentUri():String {
|
||||
// TODO: Using Uri.parse here is throwing error with jackson
|
||||
var audioTrack = audioTracks[0]
|
||||
return "$serverUrl${audioTrack.contentUrl}?token=$token"
|
||||
}
|
||||
fun getMimeType():String {
|
||||
var audioTrack = audioTracks[0]
|
||||
return audioTrack.mimeType
|
||||
}
|
||||
fun getMediaMetadataCompat(): MediaMetadataCompat {
|
||||
var metadata = mediaMetadata as BookMetadata
|
||||
|
||||
var metadataBuilder = MediaMetadataCompat.Builder()
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, metadata.title)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, metadata.title)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, metadata.authors.joinToString(",") { it.name })
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, metadata.authors.joinToString(",") { it.name })
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, metadata.authors.joinToString(",") { it.name })
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, "series")
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, id)
|
||||
return metadataBuilder.build()
|
||||
}
|
||||
fun getMediaMetadata(): MediaMetadata {
|
||||
var metadata = mediaMetadata as BookMetadata
|
||||
var authorName = metadata.authors.joinToString(",") { it.name }
|
||||
var metadataBuilder = MediaMetadata.Builder()
|
||||
.setTitle(metadata.title)
|
||||
.setDisplayTitle(metadata.title)
|
||||
.setArtist(authorName)
|
||||
.setAlbumArtist(authorName)
|
||||
.setSubtitle(authorName)
|
||||
|
||||
// var contentUri = this.getContentUri()
|
||||
// metadataBuilder.setMediaUri(contentUri)
|
||||
|
||||
return metadataBuilder.build()
|
||||
}
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class AudioTrack(
|
||||
var index:Int,
|
||||
@@ -207,5 +149,6 @@ data class AudioTrack(
|
||||
var duration:Double,
|
||||
var title:String,
|
||||
var contentUrl:String,
|
||||
var mimeType:String
|
||||
var mimeType:String,
|
||||
var isLocal:Boolean
|
||||
)
|
||||
|
||||
@@ -13,6 +13,15 @@ import org.json.JSONObject
|
||||
class DbManager : Plugin() {
|
||||
val tag = "DbManager"
|
||||
|
||||
fun loadDeviceData():DeviceData {
|
||||
var deviceData:DeviceData? = Paper.book("device").read("data")
|
||||
return deviceData ?: DeviceData(mutableListOf(),null)
|
||||
}
|
||||
|
||||
fun saveDeviceData(deviceData:DeviceData) {
|
||||
Paper.book("device").write("data", deviceData)
|
||||
}
|
||||
|
||||
fun saveObject(db:String, key:String, value:JSONObject) {
|
||||
Log.d(tag, "Saving Object $key ${value.toString()}")
|
||||
Paper.book(db).write(key, value)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.audiobookshelf.app.data
|
||||
|
||||
import android.net.Uri
|
||||
import com.getcapacitor.JSObject
|
||||
|
||||
data class ServerConfig(
|
||||
var id:String,
|
||||
var index:Int,
|
||||
var name:String,
|
||||
var address:String,
|
||||
var username:String,
|
||||
var token:String
|
||||
)
|
||||
|
||||
data class DeviceData(
|
||||
var serverConfigs:MutableList<ServerConfig>,
|
||||
var lastServerConfigId:String?
|
||||
)
|
||||
|
||||
data class LocalMediaItem(
|
||||
val name: String,
|
||||
val simplePath: String,
|
||||
val audioTracks:MutableList<AudioTrack>
|
||||
)
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.audiobookshelf.app.data
|
||||
|
||||
import android.net.Uri
|
||||
import android.support.v4.media.MediaMetadataCompat
|
||||
import com.audiobookshelf.app.R
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||
import com.google.android.exoplayer2.MediaMetadata
|
||||
|
||||
val PLAYMETHOD_DIRECTPLAY = 0
|
||||
val PLAYMETHOD_DIRECTSTREAM = 1
|
||||
val PLAYMETHOD_TRANSCODE = 2
|
||||
val PLAYMETHOD_LOCAL = 3
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
class PlaybackSession(
|
||||
var id:String,
|
||||
var userId:String,
|
||||
var libraryItemId:String,
|
||||
var episodeId:String,
|
||||
var mediaEntityId:String,
|
||||
var mediaType:String,
|
||||
var mediaMetadata:MediaTypeMetadata,
|
||||
var coverPath:String?,
|
||||
var duration:Double,
|
||||
var playMethod:Int,
|
||||
var audioTracks:MutableList<AudioTrack>,
|
||||
var currentTime:Double,
|
||||
var libraryItem:LibraryItem,
|
||||
var serverUrl:String,
|
||||
var token:String
|
||||
) {
|
||||
|
||||
@JsonIgnore
|
||||
fun getIsHls():Boolean {
|
||||
return playMethod == PLAYMETHOD_TRANSCODE
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
fun getTitle():String {
|
||||
if (mediaMetadata == null) return "Unset"
|
||||
var metadata = mediaMetadata as BookMetadata
|
||||
return metadata.title
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
fun getAuthor():String {
|
||||
if (mediaMetadata == null) return "Unset"
|
||||
var metadata = mediaMetadata as BookMetadata
|
||||
return metadata.authors.joinToString(",") { it.name }
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
fun getCoverUri(): Uri {
|
||||
if (coverPath == null) return Uri.parse("android.resource://com.audiobookshelf.app/" + R.drawable.icon)
|
||||
return Uri.parse("$serverUrl/api/items/$libraryItemId/cover?token=$token")
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
fun getContentUri(): Uri {
|
||||
var audioTrack = audioTracks[0]
|
||||
return Uri.parse("$serverUrl${audioTrack.contentUrl}?token=$token")
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
fun getMimeType():String {
|
||||
var audioTrack = audioTracks[0]
|
||||
return audioTrack.mimeType
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
fun getMediaMetadataCompat(): MediaMetadataCompat {
|
||||
var metadataBuilder = MediaMetadataCompat.Builder()
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, this.getTitle())
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, getTitle())
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, this.getAuthor())
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, this.getAuthor())
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, this.getAuthor())
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, "series")
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, id)
|
||||
return metadataBuilder.build()
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
fun getExoMediaMetadata(): MediaMetadata {
|
||||
var authorName = this.getAuthor()
|
||||
var metadataBuilder = MediaMetadata.Builder()
|
||||
.setTitle(this.getTitle())
|
||||
.setDisplayTitle(this.getTitle())
|
||||
.setArtist(authorName)
|
||||
.setAlbumArtist(authorName)
|
||||
.setSubtitle(authorName)
|
||||
|
||||
var contentUri = this.getContentUri()
|
||||
metadataBuilder.setMediaUri(contentUri)
|
||||
|
||||
return metadataBuilder.build()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.audiobookshelf.app.device
|
||||
|
||||
import android.util.Log
|
||||
import com.audiobookshelf.app.data.DbManager
|
||||
import com.audiobookshelf.app.data.DeviceData
|
||||
import com.audiobookshelf.app.data.ServerConfig
|
||||
|
||||
object DeviceManager {
|
||||
val tag = "DeviceManager"
|
||||
val dbManager:DbManager = DbManager()
|
||||
var deviceData:DeviceData = dbManager.loadDeviceData()
|
||||
var currentServerConfig: ServerConfig? = null
|
||||
|
||||
val serverAddress get() = currentServerConfig?.address ?: ""
|
||||
val token get() = currentServerConfig?.token ?: ""
|
||||
|
||||
init {
|
||||
Log.d(tag, "Device Manager Singleton invoked")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.audiobookshelf.app.device
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import com.anggrayudi.storage.file.*
|
||||
import com.arthenica.ffmpegkit.FFmpegKitConfig
|
||||
import com.arthenica.ffmpegkit.FFprobeKit
|
||||
import com.arthenica.ffmpegkit.FFprobeSession
|
||||
import com.arthenica.ffmpegkit.Level
|
||||
import com.audiobookshelf.app.data.AudioProbeResult
|
||||
import com.audiobookshelf.app.data.AudioTrack
|
||||
import com.audiobookshelf.app.data.LocalMediaItem
|
||||
import com.audiobookshelf.app.data.PlaybackSession
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
|
||||
|
||||
class FolderScanner(var ctx: Context) {
|
||||
private val tag = "FolderScanner"
|
||||
|
||||
fun scanForAudiobooks(folderUrl: String):MutableList<LocalMediaItem> {
|
||||
var df: DocumentFile? = DocumentFileCompat.fromUri(ctx, Uri.parse(folderUrl))
|
||||
|
||||
if (df == null) {
|
||||
Log.e(tag, "Folder Doc File Invalid $folderUrl")
|
||||
return mutableListOf()
|
||||
}
|
||||
|
||||
var mediaFolders = mutableListOf<LocalMediaItem>()
|
||||
var foldersFound = df.search(false, DocumentFileType.FOLDER)
|
||||
|
||||
foldersFound.forEach {
|
||||
Log.d(tag, "Iterating over Folder Found ${it.name} | ${it.getSimplePath(ctx)} | URI: ${it.uri}")
|
||||
var folderName = it.name ?: ""
|
||||
var mediaFiles = mutableListOf<LocalMediaItem>()
|
||||
|
||||
var audioTracks = mutableListOf<AudioTrack>()
|
||||
var index = 1
|
||||
|
||||
var filesInFolder = it.search(false, DocumentFileType.FILE, arrayOf("audio/*", "image/*"))
|
||||
filesInFolder.forEach { it2 ->
|
||||
var mimeType = it2?.mimeType ?: ""
|
||||
var filename = it2?.name ?: ""
|
||||
var isAudio = mimeType.startsWith("audio")
|
||||
Log.d(tag, "Found $mimeType file $filename in folder $folderName")
|
||||
|
||||
if (isAudio) {
|
||||
var absolutePath = it2.getAbsolutePath(ctx)
|
||||
Log.d(tag, "Audio File Path $absolutePath")
|
||||
|
||||
// TODO: Make asynchronous
|
||||
var session = FFprobeKit.execute("-i \"$absolutePath\" -print_format json -show_format -show_streams -select_streams a -show_chapters -loglevel quiet")
|
||||
var sessionData = session.output
|
||||
Log.d(tag, "AFTER FFPROBE STRING $sessionData")
|
||||
|
||||
val mapper = jacksonObjectMapper()
|
||||
val audioProbeResult = mapper.readValue<AudioProbeResult>(sessionData)
|
||||
Log.d(tag, "Probe Result DATA ${audioProbeResult.duration} | ${audioProbeResult.size} | ${audioProbeResult.title} | ${audioProbeResult.artist}")
|
||||
|
||||
var track = AudioTrack(index, 0.0, 0.0, filename, absolutePath, mimeType, true)
|
||||
audioTracks.add(track)
|
||||
} else {
|
||||
Log.d(tag, "Found non audio file $filename")
|
||||
}
|
||||
// var imageFile = StorageManager.MediaFile(it2.uri, filename, it2.getSimplePath(context), it2.length(), mimeType, isAudio)
|
||||
// mediaFiles.add(imageFile)
|
||||
}
|
||||
if (mediaFiles.size > 0) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return mediaFolders
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import com.audiobookshelf.app.data.Library
|
||||
import com.audiobookshelf.app.data.LibraryItem
|
||||
import com.audiobookshelf.app.data.MediaTypeMetadata
|
||||
import com.audiobookshelf.app.data.PlaybackSession
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
|
||||
Reference in New Issue
Block a user