Skip to content
This repository has been archived by the owner on Aug 7, 2024. It is now read-only.

Commit

Permalink
feat: directly open audio files within app (closes #100)
Browse files Browse the repository at this point in the history
  • Loading branch information
SuhasDissa committed Apr 14, 2024
1 parent 64b8a96 commit 6a6b48a
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 3 deletions.
33 changes: 33 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.MUSIC_PLAYER" />

<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.APP_MUSIC" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>

<intent-filter>
Expand Down Expand Up @@ -65,6 +68,36 @@
android:scheme="https"
tools:ignore="IntentFilterUniqueDataAttributes" />
</intent-filter>

<intent-filter>
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />

<data android:scheme="content" />
<data android:mimeType="audio/*" />
<data android:mimeType="application/ogg" />
<data android:mimeType="application/x-ogg" />
<data android:mimeType="application/itunes" />
</intent-filter>

<intent-filter>
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />

<data android:scheme="file" />
<data android:mimeType="audio/*" />
<data android:mimeType="application/ogg" />
<data android:mimeType="application/x-ogg" />
<data android:mimeType="application/itunes" />
</intent-filter>

<intent-filter>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>

<intent-filter>
<action android:name="android.intent.action.SEND" />

Expand Down
7 changes: 5 additions & 2 deletions app/src/main/java/app/suhasdissa/vibeyou/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,11 @@ class MainActivity : ComponentActivity() {
private fun handleIntent(intent: Intent) {
when (intent.action) {
Intent.ACTION_VIEW -> {
val uri = intent.data
uri?.let {
val uri = intent.data ?: return
// Check if uri points to a device file
if (uri.scheme == "file" || uri.scheme == "content") {
playerViewModel.tryToPlayUri(uri)
} else {
processLink(uri)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.content.ContentResolver
import android.content.ContentUris
import android.net.Uri
import android.os.Build
import android.os.Environment.getExternalStorageDirectory
import android.provider.MediaStore
import android.text.format.DateUtils
import androidx.core.net.toUri
Expand All @@ -16,6 +17,7 @@ import app.suhasdissa.vibeyou.backend.database.entities.SearchQuery
import app.suhasdissa.vibeyou.utils.Pref
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File

class LocalMusicRepository(
private val contentResolver: ContentResolver,
Expand All @@ -26,6 +28,7 @@ class LocalMusicRepository(
private var artistCache = listOf<Artist>()

suspend fun getAllSongs(): List<Song> = withContext(Dispatchers.IO) {

if (songsCache.isNotEmpty()) return@withContext songsCache

val songs = mutableListOf<Song>()
Expand Down Expand Up @@ -74,7 +77,8 @@ class LocalMusicRepository(
MediaStore.Audio.Media.DATE_MODIFIED
)
val dateAddedColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATE_ADDED)
val trackNumberColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.CD_TRACK_NUMBER)
val trackNumberColumn =
cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.CD_TRACK_NUMBER)

while (cursor.moveToNext()) {
val id = cursor.getLong(idColumn)
Expand Down Expand Up @@ -122,6 +126,136 @@ class LocalMusicRepository(
songs
}

suspend fun getSongFromUri(uri: Uri): Song? = withContext(Dispatchers.IO) {
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 projection = arrayOf(
MediaStore.Audio.Media._ID,
MediaStore.Audio.Media.TITLE,
MediaStore.Audio.Media.DISPLAY_NAME,
MediaStore.Audio.Media.DURATION,
MediaStore.Audio.Media.ARTIST,
MediaStore.Audio.Media.ALBUM_ID,
MediaStore.Audio.Media.ARTIST_ID,
MediaStore.Audio.Media.DATE_MODIFIED,
MediaStore.Audio.Media.DATE_ADDED,
MediaStore.Audio.Media.CD_TRACK_NUMBER
)

val selection = "${MediaStore.Audio.Media.DATA} = ?"

val path = getPathFromContentUri(uri)
val selectionArgs = arrayOf(path)

val query = contentResolver.query(
collection,
projection,
selection,
selectionArgs,
null
)

query?.use { cursor ->
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID)
val titleColumn =
cursor.getColumnIndex(MediaStore.Audio.Media.TITLE)
val nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME)
val durationColumn =
cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION)
val artistColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST)
val albumColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID)
val artistIdColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID)
val creationDateColumn = cursor.getColumnIndexOrThrow(
MediaStore.Audio.Media.DATE_MODIFIED
)
val dateAddedColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATE_ADDED)
val trackNumberColumn =
cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.CD_TRACK_NUMBER)

if (cursor.moveToFirst()) {
val id = cursor.getLong(idColumn)
val name =
if (cursor.isNull(titleColumn)) {
cursor.getString(nameColumn)
} else {
cursor.getString(
titleColumn
)
}
val albumId = cursor.getLong(albumColumn)

val contentUri: Uri = ContentUris.withAppendedId(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
id
)

val duration = cursor.getLong(durationColumn) / 1000
if (duration == 0L) return@withContext null

return@withContext Song(
id = contentUri.toString(),
title = name,
durationText = DateUtils.formatElapsedTime(duration),
thumbnailUri = getAlbumArt(albumId),
artistsText = cursor.getString(artistColumn),
albumId = albumId,
artistId = cursor.getLong(artistIdColumn),
isLocal = true,
creationDate = cursor.getLong(creationDateColumn),
dateAdded = cursor.getLong(dateAddedColumn),
trackNumber = cursor.getLong(trackNumberColumn)
)
}
}
return@withContext null
}

private fun getPathFromContentUri(uri: Uri): String? {
var songFile: File? = null
if (uri.authority == "com.android.externalstorage.documents") {
val path = uri.path?.split(":".toRegex(), 2)?.get(1)
if (path != null) {
songFile = File(getExternalStorageDirectory(), path)
}
}
if (songFile == null) {
val path = getFilePathFromUri(uri)
if (path != null)
songFile = File(path)
}
if (songFile == null && uri.path != null) {
songFile = File(uri.path!!)
}
if (songFile != null) {
return songFile.absolutePath
}
return null
}

private fun getFilePathFromUri(uri: Uri): String? {
val column = MediaStore.Audio.Media.DATA
val projection = arrayOf(column)
val query = contentResolver.query(uri, projection, null, null, null)

query?.use { cursor ->
try {
if (cursor.moveToFirst()) {
val columnIndex = cursor.getColumnIndexOrThrow(column)
return cursor.getString(columnIndex)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
return null
}

suspend fun getAllAlbums(): List<Album> = withContext(Dispatchers.IO) {
if (albumCache.isNotEmpty()) return@withContext albumCache

Expand Down Expand Up @@ -279,6 +413,7 @@ class LocalMusicRepository(

searchDao.addSearchQuery(SearchQuery(id = 0, query))
}

fun deleteQuery(query: String) = searchDao.deleteQuery(query)
fun getSearchHistory() = searchDao.getSearchHistory()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package app.suhasdissa.vibeyou.backend.viewmodel

import android.net.Uri
import android.os.Handler
import android.os.Looper
import android.util.Log
Expand All @@ -17,6 +18,7 @@ import androidx.media3.common.PlaybackParameters
import androidx.media3.session.MediaController
import app.suhasdissa.vibeyou.MellowMusicApplication
import app.suhasdissa.vibeyou.backend.data.Song
import app.suhasdissa.vibeyou.backend.repository.LocalMusicRepository
import app.suhasdissa.vibeyou.backend.repository.PipedMusicRepository
import app.suhasdissa.vibeyou.backend.repository.SongDatabaseRepository
import app.suhasdissa.vibeyou.utils.addNext
Expand All @@ -33,6 +35,7 @@ import kotlinx.coroutines.launch
class PlayerViewModel(
private val songDatabaseRepository: SongDatabaseRepository,
private val musicRepository: PipedMusicRepository,
private val localMusicRepository: LocalMusicRepository,
private val controllerFuture: ListenableFuture<MediaController>
) :
ViewModel() {
Expand Down Expand Up @@ -148,13 +151,27 @@ class PlayerViewModel(
}
}

fun tryToPlayUri(uri: Uri) {
viewModelScope.launch {
val song: Song? = localMusicRepository.getSongFromUri(uri)
song?.let {
if (controller == null) {
toBePlayed = song.asMediaItem
} else {
controller?.forcePlay(song.asMediaItem)
}
}
}
}

companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer {
val application = (this[APPLICATION_KEY] as MellowMusicApplication)
PlayerViewModel(
application.container.songDatabaseRepository,
application.container.pipedMusicRepository,
application.container.localMusicRepository,
application.container.controllerFuture
)
}
Expand Down

0 comments on commit 6a6b48a

Please sign in to comment.