New data model classes, ffmpeg-kit, jackson json deserializer, add permission

This commit is contained in:
advplyr
2022-03-28 19:53:53 -05:00
parent 461733854a
commit 4fc70cd3dd
30 changed files with 9058 additions and 9642 deletions
+1 -1
View File
@@ -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