mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-08 21:08:43 +02:00
Offline support, downloading, syncing progress
This commit is contained in:
@@ -10,8 +10,8 @@ android {
|
||||
applicationId "com.audiobookshelf.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 8
|
||||
versionName "0.4.0-beta"
|
||||
versionCode 9
|
||||
versionName "0.8.0-beta"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
@@ -9,8 +9,12 @@ android {
|
||||
|
||||
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
||||
dependencies {
|
||||
implementation project(':capacitor-community-sqlite')
|
||||
implementation project(':capacitor-dialog')
|
||||
implementation project(':capacitor-network')
|
||||
implementation project(':capacitor-storage')
|
||||
implementation project(':robingenz-capacitor-app-update')
|
||||
implementation project(':capacitor-data-storage-sqlite')
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,26 @@
|
||||
[
|
||||
{
|
||||
"pkg": "@capacitor-community/sqlite",
|
||||
"classpath": "com.getcapacitor.community.database.sqlite.CapacitorSQLitePlugin"
|
||||
},
|
||||
{
|
||||
"pkg": "@capacitor/dialog",
|
||||
"classpath": "com.capacitorjs.plugins.dialog.DialogPlugin"
|
||||
},
|
||||
{
|
||||
"pkg": "@capacitor/network",
|
||||
"classpath": "com.capacitorjs.plugins.network.NetworkPlugin"
|
||||
},
|
||||
{
|
||||
"pkg": "@capacitor/storage",
|
||||
"classpath": "com.capacitorjs.plugins.storage.StoragePlugin"
|
||||
},
|
||||
{
|
||||
"pkg": "@robingenz/capacitor-app-update",
|
||||
"classpath": "dev.robingenz.capacitor.appupdate.AppUpdatePlugin"
|
||||
},
|
||||
{
|
||||
"pkg": "capacitor-data-storage-sqlite",
|
||||
"classpath": "com.jeep.plugin.capacitor.capacitordatastoragesqlite.CapacitorDataStorageSqlitePlugin"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -0,0 +1,320 @@
|
||||
package com.audiobookshelf.app
|
||||
|
||||
import android.app.DownloadManager
|
||||
import android.content.ContentUris
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import com.getcapacitor.JSObject
|
||||
import com.getcapacitor.Plugin
|
||||
import com.getcapacitor.PluginCall
|
||||
import com.getcapacitor.PluginMethod
|
||||
import com.getcapacitor.annotation.CapacitorPlugin
|
||||
import java.io.File
|
||||
|
||||
|
||||
@CapacitorPlugin(name = "AudioDownloader")
|
||||
class AudioDownloader : Plugin() {
|
||||
private val tag = "AudioDownloader"
|
||||
|
||||
lateinit var mainActivity:MainActivity
|
||||
lateinit var downloadManager:DownloadManager
|
||||
|
||||
data class AudiobookDownload(val url: String, val filename:String, val downloadId: Long)
|
||||
var downloads:MutableList<AudiobookDownload> = mutableListOf()
|
||||
|
||||
data class CoverItem(val name:String, val coverUrl:String)
|
||||
data class AudiobookItem(val id: Long, val uri: Uri, val name: String, val size: Int, val duration: Int, val coverUrl: String) {
|
||||
fun toJSObject() : JSObject {
|
||||
var obj = JSObject()
|
||||
obj.put("id", this.id)
|
||||
obj.put("uri", this.uri)
|
||||
obj.put("name", this.name)
|
||||
obj.put("size", this.size)
|
||||
obj.put("duration", this.duration)
|
||||
obj.put("coverUrl", this.coverUrl)
|
||||
return obj
|
||||
}
|
||||
}
|
||||
var audiobookItems:MutableList<AudiobookItem> = mutableListOf()
|
||||
|
||||
override fun load() {
|
||||
mainActivity = (activity as MainActivity)
|
||||
downloadManager = activity.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
|
||||
var recieverEvent : (evt: String, id: Long) -> Unit = { evt: String, id: Long ->
|
||||
Log.d(tag, "RECEIVE EVT $evt $id")
|
||||
if (evt == "complete") {
|
||||
var path = downloadManager.getUriForDownloadedFile(id)
|
||||
|
||||
var download = downloads.find { it.downloadId == id }
|
||||
var filename = download?.filename
|
||||
|
||||
var jsobj = JSObject()
|
||||
jsobj.put("downloadId", id)
|
||||
jsobj.put("contentUrl", path)
|
||||
jsobj.put("filename", filename)
|
||||
notifyListeners("onDownloadComplete", jsobj)
|
||||
downloads = downloads.filter { it.downloadId != id } as MutableList<AudiobookDownload>
|
||||
}
|
||||
if (evt == "clicked") {
|
||||
Log.d(tag, "Clicked $id back in the audiodownloader")
|
||||
}
|
||||
}
|
||||
|
||||
mainActivity.registerBroadcastReceiver(recieverEvent)
|
||||
}
|
||||
|
||||
fun loadAudiobooks() {
|
||||
var covers = loadCovers()
|
||||
|
||||
val projection = arrayOf(
|
||||
MediaStore.Audio.Media._ID,
|
||||
MediaStore.Audio.Media.DISPLAY_NAME,
|
||||
MediaStore.Audio.Media.DURATION,
|
||||
MediaStore.Audio.Media.SIZE,
|
||||
MediaStore.Audio.Media.IS_AUDIOBOOK
|
||||
)
|
||||
|
||||
var _audiobookItems:MutableList<AudiobookItem> = mutableListOf()
|
||||
val selection = "${MediaStore.Audio.Media.IS_AUDIOBOOK} == ?"
|
||||
val selectionArgs = arrayOf("1")
|
||||
val sortOrder = "${MediaStore.Audio.Media.DISPLAY_NAME} ASC"
|
||||
|
||||
activity.applicationContext.contentResolver.query(
|
||||
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
|
||||
projection,
|
||||
selection,
|
||||
selectionArgs,
|
||||
sortOrder
|
||||
)?.use { cursor ->
|
||||
|
||||
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID)
|
||||
val nameColumn =
|
||||
cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME)
|
||||
val durationColumn =
|
||||
cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION)
|
||||
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.SIZE)
|
||||
val isAudiobookColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.IS_AUDIOBOOK)
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
val id = cursor.getLong(idColumn)
|
||||
val name = cursor.getString(nameColumn)
|
||||
val duration = cursor.getInt(durationColumn)
|
||||
val size = cursor.getInt(sizeColumn)
|
||||
var isAudiobook = cursor.getInt(isAudiobookColumn)
|
||||
|
||||
if (isAudiobook == 1) {
|
||||
val contentUri: Uri = ContentUris.withAppendedId(
|
||||
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
|
||||
id
|
||||
)
|
||||
|
||||
Log.d(tag, "Got Content FRom MEdia STORE $id $contentUri, Name: $name, Dur: $duration, Size: $size")
|
||||
var audiobookId = File(name).nameWithoutExtension
|
||||
var coverItem:CoverItem? = covers.find{it.name == audiobookId}
|
||||
var coverUrl = coverItem?.coverUrl ?: ""
|
||||
|
||||
_audiobookItems.add(AudiobookItem(id, contentUri, name, duration, size, coverUrl))
|
||||
}
|
||||
}
|
||||
audiobookItems = _audiobookItems
|
||||
|
||||
var audiobookObjs:List<JSObject> = _audiobookItems.map{ it.toJSObject() }
|
||||
|
||||
var mediaItemNoticePayload = JSObject()
|
||||
mediaItemNoticePayload.put("items", audiobookObjs)
|
||||
notifyListeners("onMediaLoaded", mediaItemNoticePayload)
|
||||
}
|
||||
}
|
||||
|
||||
fun loadCovers() : MutableList<CoverItem> {
|
||||
val projection = arrayOf(
|
||||
MediaStore.Images.Media._ID,
|
||||
MediaStore.Images.Media.DISPLAY_NAME
|
||||
)
|
||||
val sortOrder = "${MediaStore.Images.Media.DISPLAY_NAME} ASC"
|
||||
|
||||
var coverItems:MutableList<CoverItem> = mutableListOf()
|
||||
|
||||
activity.applicationContext.contentResolver.query(
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||
projection,
|
||||
null,
|
||||
null,
|
||||
sortOrder
|
||||
)?.use { cursor ->
|
||||
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
|
||||
val nameColumn =
|
||||
cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
val id = cursor.getLong(idColumn)
|
||||
val filename = cursor.getString(nameColumn)
|
||||
val contentUri: Uri = ContentUris.withAppendedId(
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||
id
|
||||
)
|
||||
|
||||
var name = File(filename).nameWithoutExtension
|
||||
Log.d(tag, "Got IMAGE FRom Media STORE $id $contentUri, Name: $name")
|
||||
|
||||
var coverItem = CoverItem(name, contentUri.toString())
|
||||
coverItems.add(coverItem)
|
||||
}
|
||||
}
|
||||
return coverItems
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun load(call: PluginCall) {
|
||||
loadAudiobooks()
|
||||
call.resolve()
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun downloadCover(call: PluginCall) {
|
||||
var url = call.data.getString("downloadUrl", "unknown").toString()
|
||||
var title = call.data.getString("title", "Cover").toString()
|
||||
var filename = call.data.getString("filename", "audiobook.jpg").toString()
|
||||
|
||||
Log.d(tag, "Called download cover: $url")
|
||||
|
||||
var dlRequest = DownloadManager.Request(Uri.parse(url))
|
||||
dlRequest.setTitle("Cover Art: $title")
|
||||
dlRequest.setDescription("Cover art for audiobook")
|
||||
dlRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION)
|
||||
dlRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_AUDIOBOOKS, filename)
|
||||
var downloadId = downloadManager.enqueue(dlRequest)
|
||||
|
||||
var progressReceiver : (prog: Long) -> Unit = { prog: Long ->
|
||||
//
|
||||
}
|
||||
|
||||
var doneReceiver : (success: Boolean) -> Unit = { success: Boolean ->
|
||||
var jsobj = JSObject()
|
||||
if (success) {
|
||||
var path = downloadManager.getUriForDownloadedFile(downloadId)
|
||||
jsobj.put("url", path)
|
||||
call.resolve(jsobj)
|
||||
} else {
|
||||
jsobj.put("failed", true)
|
||||
call.resolve(jsobj)
|
||||
}
|
||||
}
|
||||
|
||||
var progressUpdater = DownloadProgressUpdater(downloadManager, downloadId, progressReceiver, doneReceiver)
|
||||
progressUpdater.run()
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun download(call: PluginCall) {
|
||||
var url = call.data.getString("downloadUrl", "unknown").toString()
|
||||
var title = call.data.getString("title", "Audiobook").toString()
|
||||
var filename = call.data.getString("filename", "audiobook.mp3").toString()
|
||||
|
||||
Log.d(tag, "Called download: $url")
|
||||
|
||||
var dlRequest = DownloadManager.Request(Uri.parse(url))
|
||||
dlRequest.setTitle(title)
|
||||
dlRequest.setDescription("Downloading to Audiobooks directory")
|
||||
dlRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION)
|
||||
|
||||
dlRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_AUDIOBOOKS, filename)
|
||||
|
||||
var downloadId = downloadManager.enqueue(dlRequest)
|
||||
|
||||
var download = AudiobookDownload(url, filename, downloadId)
|
||||
downloads.add(download)
|
||||
|
||||
var progressReceiver : (prog: Long) -> Unit = { prog: Long ->
|
||||
var jsobj = JSObject()
|
||||
jsobj.put("filename", filename)
|
||||
jsobj.put("downloadId", downloadId)
|
||||
jsobj.put("progress", prog)
|
||||
notifyListeners("onDownloadProgress", jsobj)
|
||||
}
|
||||
|
||||
var doneReceiver : (success: Boolean) -> Unit = { success: Boolean ->
|
||||
Log.d(tag, "RECIEVER DONE, SUCCES? $success")
|
||||
}
|
||||
|
||||
var progressUpdater = DownloadProgressUpdater(downloadManager, downloadId, progressReceiver, doneReceiver)
|
||||
progressUpdater.run()
|
||||
|
||||
val ret = JSObject()
|
||||
ret.put("value", downloadId)
|
||||
call.resolve(ret)
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun delete(call:PluginCall) {
|
||||
var filename = call.data.getString("filename", "audiobook.mp3").toString()
|
||||
var url = call.data.getString("url", "").toString()
|
||||
var coverUrl = call.data.getString("coverUrl", "").toString()
|
||||
|
||||
Log.d(tag, "Called delete file $filename $url")
|
||||
|
||||
var contentResolver = activity.applicationContext.contentResolver
|
||||
contentResolver.delete(Uri.parse(url), null, null)
|
||||
|
||||
if (coverUrl != "") {
|
||||
contentResolver.delete(Uri.parse(coverUrl), null, null)
|
||||
}
|
||||
|
||||
call.resolve()
|
||||
}
|
||||
|
||||
internal class DownloadProgressUpdater(private val manager: DownloadManager, private val downloadId: Long, private var receiver:(Long) -> Unit, private var doneReceiver:(Boolean) -> Unit) : Thread() {
|
||||
private val query: DownloadManager.Query = DownloadManager.Query()
|
||||
private var totalBytes: Int = 0
|
||||
private var TAG = "DownloadProgressUpdater"
|
||||
|
||||
init {
|
||||
query.setFilterById(this.downloadId)
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
Log.d(TAG, "RUN FOR ID $downloadId")
|
||||
var keepRunning = true
|
||||
while (keepRunning) {
|
||||
Thread.sleep(500)
|
||||
|
||||
manager.query(query).use {
|
||||
if (it.moveToFirst()) {
|
||||
|
||||
//get total bytes of the file
|
||||
if (totalBytes <= 0) {
|
||||
totalBytes = it.getInt(it.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
|
||||
}
|
||||
|
||||
val downloadStatus = it.getInt(it.getColumnIndex(DownloadManager.COLUMN_STATUS))
|
||||
val bytesDownloadedSoFar = it.getInt(it.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
|
||||
|
||||
if (downloadStatus == DownloadManager.STATUS_SUCCESSFUL || downloadStatus == DownloadManager.STATUS_FAILED) {
|
||||
if (downloadStatus == DownloadManager.STATUS_SUCCESSFUL) {
|
||||
doneReceiver(true)
|
||||
} else {
|
||||
doneReceiver(false)
|
||||
}
|
||||
keepRunning = false
|
||||
this.interrupt()
|
||||
} else {
|
||||
//update progress
|
||||
val percentProgress = ((bytesDownloadedSoFar * 100L) / totalBytes)
|
||||
receiver(percentProgress)
|
||||
}
|
||||
|
||||
} else {
|
||||
Log.e(TAG, "NOT FOUND IN QUERY")
|
||||
keepRunning = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -16,10 +16,14 @@ class Audiobook {
|
||||
var playbackSpeed:Float = 1f
|
||||
var duration:Long = 0
|
||||
|
||||
var isLocal:Boolean = false
|
||||
var contentUrl:String = ""
|
||||
|
||||
var hasPlayerLoaded:Boolean = false
|
||||
|
||||
val playlistUri:Uri
|
||||
val coverUri:Uri
|
||||
var playlistUri:Uri = Uri.EMPTY
|
||||
var coverUri:Uri = Uri.EMPTY
|
||||
var contentUri:Uri = Uri.EMPTY // For Local only
|
||||
|
||||
constructor(jsondata:JSObject) {
|
||||
id = jsondata.getString("id", "audiobook").toString()
|
||||
@@ -34,7 +38,22 @@ class Audiobook {
|
||||
playbackSpeed = jsondata.getDouble("playbackSpeed")!!.toFloat()
|
||||
duration = jsondata.getString("duration", "0")!!.toLong()
|
||||
|
||||
playlistUri = Uri.parse(playlistUrl)
|
||||
coverUri = Uri.parse(cover)
|
||||
// Local data
|
||||
isLocal = jsondata.getBoolean("isLocal", false) == true
|
||||
contentUrl = jsondata.getString("contentUrl", "").toString()
|
||||
|
||||
if (playlistUrl != "") {
|
||||
playlistUri = Uri.parse(playlistUrl)
|
||||
}
|
||||
if (cover != "") {
|
||||
coverUri = Uri.parse(cover)
|
||||
} else {
|
||||
coverUri = Uri.parse("android.resource://com.audiobookshelf.app/" + R.drawable.icon)
|
||||
cover = coverUri.toString()
|
||||
}
|
||||
|
||||
if (contentUrl != "") {
|
||||
contentUri = Uri.parse(contentUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
package com.audiobookshelf.app
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.app.DownloadManager
|
||||
import android.content.*
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import com.example.myapp.MyNativeAudio
|
||||
import android.widget.Toast
|
||||
import com.getcapacitor.BridgeActivity
|
||||
|
||||
|
||||
class MainActivity : BridgeActivity() {
|
||||
private val tag = "MainActivity"
|
||||
|
||||
@@ -18,11 +19,45 @@ class MainActivity : BridgeActivity() {
|
||||
private lateinit var mConnection : ServiceConnection
|
||||
|
||||
lateinit var pluginCallback : () -> Unit
|
||||
lateinit var downloaderCallback : (String, Long) -> Unit
|
||||
|
||||
val broadcastReceiver = object: BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
when (intent?.action) {
|
||||
DownloadManager.ACTION_DOWNLOAD_COMPLETE -> {
|
||||
var thisdlid = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0L)
|
||||
|
||||
downloaderCallback("complete", thisdlid)
|
||||
|
||||
Log.d(tag, "DOWNNLAOD COMPELTE $thisdlid")
|
||||
Toast.makeText(this@MainActivity, "Download Completed $thisdlid", Toast.LENGTH_SHORT)
|
||||
}
|
||||
DownloadManager.ACTION_NOTIFICATION_CLICKED -> {
|
||||
var thisdlid = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0L)
|
||||
downloaderCallback("clicked", thisdlid)
|
||||
|
||||
Log.d(tag, "CLICKED NOTFIFICAIONT $thisdlid")
|
||||
Toast.makeText(this@MainActivity, "Download CLICKED $thisdlid", Toast.LENGTH_SHORT)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Log.d(tag, "onCreate")
|
||||
registerPlugin(MyNativeAudio::class.java)
|
||||
registerPlugin(AudioDownloader::class.java)
|
||||
|
||||
var filter = IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE).apply {
|
||||
addAction(DownloadManager.ACTION_NOTIFICATION_CLICKED)
|
||||
}
|
||||
registerReceiver(broadcastReceiver, filter)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
// unregisterReceiver(broadcastReceiver)
|
||||
}
|
||||
|
||||
override fun onPostCreate(savedInstanceState: Bundle?) {
|
||||
@@ -62,4 +97,8 @@ class MainActivity : BridgeActivity() {
|
||||
val stopIntent = Intent(this, PlayerNotificationService::class.java)
|
||||
stopService(stopIntent)
|
||||
}
|
||||
|
||||
fun registerBroadcastReceiver(cb: (String, Long) -> Unit) {
|
||||
downloaderCallback = cb
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
package com.example.myapp
|
||||
package com.audiobookshelf.app
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.audiobookshelf.app.Audiobook
|
||||
import com.audiobookshelf.app.MainActivity
|
||||
import com.audiobookshelf.app.PlayerNotificationService
|
||||
import com.getcapacitor.JSObject
|
||||
import com.getcapacitor.Plugin
|
||||
import com.getcapacitor.PluginCall
|
||||
@@ -56,12 +53,20 @@ class MyNativeAudio : Plugin() {
|
||||
} else {
|
||||
Log.w(tag, "Service already started --")
|
||||
}
|
||||
|
||||
var jsobj = JSObject()
|
||||
|
||||
var audiobook:Audiobook = Audiobook(call.data)
|
||||
if (audiobook.playlistUrl == "" && audiobook.contentUrl == "") {
|
||||
Log.e(tag, "Invalid URL for init audio player")
|
||||
|
||||
jsobj.put("success", false)
|
||||
return call.resolve(jsobj)
|
||||
}
|
||||
|
||||
Handler(Looper.getMainLooper()).post() {
|
||||
playerNotificationService.initPlayer(audiobook)
|
||||
call.resolve()
|
||||
jsobj.put("success", true)
|
||||
call.resolve(jsobj)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +114,7 @@ class MyNativeAudio : Plugin() {
|
||||
call.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun seekBackward(call: PluginCall) {
|
||||
var amount:Long = call.getString("amount", "0")!!.toLong()
|
||||
@@ -117,6 +123,7 @@ class MyNativeAudio : Plugin() {
|
||||
call.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun setPlaybackSpeed(call: PluginCall) {
|
||||
var playbackSpeed:Float = call.getFloat("speed", 1.0f)!!
|
||||
@@ -126,6 +133,7 @@ class MyNativeAudio : Plugin() {
|
||||
call.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun terminateStream(call: PluginCall) {
|
||||
Handler(Looper.getMainLooper()).post() {
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.net.Uri
|
||||
import android.os.Binder
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.provider.MediaStore
|
||||
import android.support.v4.media.MediaDescriptionCompat
|
||||
import android.support.v4.media.MediaMetadataCompat
|
||||
import android.support.v4.media.session.MediaControllerCompat
|
||||
@@ -24,10 +25,15 @@ import com.google.android.exoplayer2.*
|
||||
import com.google.android.exoplayer2.audio.AudioAttributes
|
||||
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
|
||||
import com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator
|
||||
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory
|
||||
import com.google.android.exoplayer2.source.MediaSource
|
||||
import com.google.android.exoplayer2.source.ProgressiveMediaExtractor
|
||||
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 kotlinx.coroutines.*
|
||||
import java.io.File
|
||||
|
||||
|
||||
const val NOTIFICATION_LARGE_ICON_SIZE = 144 // px
|
||||
@@ -217,13 +223,13 @@ class PlayerNotificationService : Service() {
|
||||
mediaSessionConnector = MediaSessionConnector(mediaSession)
|
||||
val queueNavigator: TimelineQueueNavigator = object : TimelineQueueNavigator(mediaSession) {
|
||||
override fun getMediaDescription(player: Player, windowIndex: Int): MediaDescriptionCompat {
|
||||
return MediaDescriptionCompat.Builder()
|
||||
var builder = MediaDescriptionCompat.Builder()
|
||||
.setMediaId(currentAudiobook!!.id)
|
||||
.setTitle(currentAudiobook!!.title)
|
||||
.setSubtitle(currentAudiobook!!.author)
|
||||
.setMediaUri(currentAudiobook!!.playlistUri)
|
||||
.setIconUri(currentAudiobook!!.coverUri)
|
||||
.build()
|
||||
return builder.build()
|
||||
}
|
||||
}
|
||||
mediaSessionConnector.setQueueNavigator(queueNavigator)
|
||||
@@ -280,6 +286,9 @@ class PlayerNotificationService : Service() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private fun setPlayerListeners() {
|
||||
mPlayer.addListener(object : Player.Listener {
|
||||
override fun onPlayerError(error: PlaybackException) {
|
||||
@@ -351,28 +360,42 @@ class PlayerNotificationService : Service() {
|
||||
Log.d(tag, "Init Player audiobook already playing")
|
||||
}
|
||||
|
||||
val metadata = MediaMetadataCompat.Builder()
|
||||
var metadataBuilder = MediaMetadataCompat.Builder()
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, currentAudiobook!!.title)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, currentAudiobook!!.title)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, currentAudiobook!!.author)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, currentAudiobook!!.author)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, currentAudiobook!!.author)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, currentAudiobook!!.series)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, currentAudiobook!!.cover)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_ART_URI, currentAudiobook!!.cover)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, currentAudiobook!!.id)
|
||||
.build()
|
||||
|
||||
if (currentAudiobook!!.cover != "") {
|
||||
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ART_URI, currentAudiobook!!.cover)
|
||||
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, currentAudiobook!!.cover)
|
||||
}
|
||||
|
||||
var metadata = metadataBuilder.build()
|
||||
mediaSession.setMetadata(metadata)
|
||||
|
||||
var mediaMetadata = MediaMetadata.Builder().build()
|
||||
var mediaItem = MediaItem.Builder().setUri(currentAudiobook!!.playlistUri).setMediaMetadata(mediaMetadata).build()
|
||||
|
||||
var dataSourceFactory = DefaultHttpDataSource.Factory()
|
||||
dataSourceFactory.setUserAgent(channelId)
|
||||
dataSourceFactory.setDefaultRequestProperties(hashMapOf("Authorization" to "Bearer ${currentAudiobook!!.token}"))
|
||||
|
||||
var mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem)
|
||||
var mediaSource:MediaSource
|
||||
if (currentAudiobook!!.isLocal) {
|
||||
Log.d(tag, "Playing Local File")
|
||||
var mediaItem = MediaItem.Builder().setUri(currentAudiobook!!.contentUri).setMediaMetadata(mediaMetadata).build()
|
||||
var dataSourceFactory = DefaultDataSourceFactory(ctx, channelId)
|
||||
mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem)
|
||||
} else {
|
||||
Log.d(tag, "Playing HLS File")
|
||||
var mediaItem = MediaItem.Builder().setUri(currentAudiobook!!.playlistUri).setMediaMetadata(mediaMetadata).build()
|
||||
var dataSourceFactory = DefaultHttpDataSource.Factory()
|
||||
dataSourceFactory.setUserAgent(channelId)
|
||||
dataSourceFactory.setDefaultRequestProperties(hashMapOf("Authorization" to "Bearer ${currentAudiobook!!.token}"))
|
||||
|
||||
mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem)
|
||||
}
|
||||
|
||||
|
||||
mPlayer.setMediaSource(mediaSource, true)
|
||||
mPlayer.prepare()
|
||||
|
||||
Reference in New Issue
Block a user