Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Multimedia
Elisa
Commits
9c36a835
Commit
9c36a835
authored
Mar 17, 2021
by
Matthieu Gallien
🎵
Browse files
gets more accurate metadata for tracks and improve reliability
parent
bd039c16
Pipeline
#54793
passed with stage
in 9 minutes and 40 seconds
Changes
3
Pipelines
2
Hide whitespace changes
Inline
Side-by-side
data/AndroidManifest.xml
View file @
9c36a835
...
...
@@ -66,7 +66,7 @@
</service>
</application>
<uses-sdk
android:minSdkVersion=
"21"
android:targetSdkVersion=
"
28
"
/>
<uses-sdk
android:minSdkVersion=
"21"
android:targetSdkVersion=
"
30
"
/>
<supports-screens
android:largeScreens=
"true"
android:normalScreens=
"true"
android:anyDensity=
"true"
android:smallScreens=
"true"
/>
<uses-permission
android:name=
"android.permission.READ_EXTERNAL_STORAGE"
/>
...
...
data/src/org/kde/elisa/ElisaActivity.java
View file @
9c36a835
...
...
@@ -15,91 +15,186 @@ import android.content.pm.PackageManager;
import
android.database.Cursor
;
import
android.content.Intent
;
import
android.provider.MediaStore
;
import
java.util.ArrayList
;
import
java.util.HashMap
;
public
class
ElisaActivity
extends
QtActivity
{
private
static
String
[]
tracksRequestedColumns
=
{
MediaStore
.
Audio
.
Media
.
_ID
,
MediaStore
.
Audio
.
Media
.
TITLE
,
MediaStore
.
Audio
.
Media
.
TRACK
,
MediaStore
.
Audio
.
Media
.
YEAR
,
MediaStore
.
Audio
.
Media
.
DURATION
,
MediaStore
.
Audio
.
Media
.
DATA
,
MediaStore
.
Audio
.
Media
.
ARTIST
,
MediaStore
.
Audio
.
Media
.
ARTIST_ID
,
MediaStore
.
Audio
.
Media
.
ALBUM
,
MediaStore
.
Audio
.
Media
.
ALBUM_ID
,
MediaStore
.
Audio
.
Media
.
ALBUM_ARTIST
,
MediaStore
.
Audio
.
Media
.
TRACK
,
MediaStore
.
Audio
.
Media
.
DURATION
,
MediaStore
.
Audio
.
Media
.
DATA
,
MediaStore
.
Audio
.
Media
.
COMPOSER
,
};
private
static
String
[]
albumsRequestedColumns
=
{
MediaStore
.
Audio
.
Albums
.
_ID
,
MediaStore
.
Audio
.
Albums
.
ALBUM
,
MediaStore
.
Audio
.
Albums
.
ALBUM_ART
,
MediaStore
.
Audio
.
Albums
.
ARTIST
,
MediaStore
.
Audio
.
Albums
.
NUMBER_OF_SONGS
,
MediaStore
.
Audio
.
Albums
.
ALBUM_ART
,
};
public
static
void
listAudioFiles
(
Context
ctx
)
public
static
ArrayList
<
TrackDataType
>
listAudioFiles
(
Context
ctx
)
{
androidMusicScanTracksStarting
();
ArrayList
<
TrackDataType
>
allTracks
=
new
ArrayList
<
TrackDataType
>
();
//Some audio may be explicitly marked as not being music
String
tracksSelection
=
MediaStore
.
Audio
.
Media
.
IS_MUSIC
+
" != 0"
;
String
tracksSortOrder
=
MediaStore
.
Audio
.
Media
.
DEFAULT_SORT_ORDER
+
" ASC"
;
HashMap
<
Integer
,
AlbumDataType
>
allAlbums
=
new
HashMap
<
Integer
,
AlbumDataType
>();
if
(
ContextCompat
.
checkSelfPermission
(
ctx
,
Manifest
.
permission
.
READ_EXTERNAL_STORAGE
)
!=
PackageManager
.
PERMISSION_GRANTED
)
{
androidMusicScanTracksFinishing
();
return
allTracks
;
}
androidMusicScanAlbumsStarting
(
);
Cursor
albumsCursor
=
ctx
.
getContentResolver
().
query
(
MediaStore
.
Audio
.
Albums
.
EXTERNAL_CONTENT_URI
,
albumsRequestedColumns
,
null
,
null
,
null
);
androidMusicScanAlbumsFinishing
();
albumsCursor
.
moveToFirst
();
return
;
if
(
albumsCursor
==
null
)
{
return
allTracks
;
}
if
(!
albumsCursor
.
moveToFirst
())
{
return
allTracks
;
}
do
{
allAlbums
.
put
(
albumsCursor
.
getInt
(
0
),
new
AlbumDataType
(
albumsCursor
.
getString
(
1
),
albumsCursor
.
getString
(
2
),
albumsCursor
.
getString
(
3
)));
}
while
(
albumsCursor
.
moveToNext
());
String
tracksSelection
=
MediaStore
.
Audio
.
Media
.
IS_MUSIC
+
" != 0"
;
String
tracksSortOrder
=
MediaStore
.
Audio
.
Media
.
DEFAULT_SORT_ORDER
+
" ASC"
;
Cursor
tracksCursor
=
ctx
.
getContentResolver
().
query
(
MediaStore
.
Audio
.
Media
.
EXTERNAL_CONTENT_URI
,
tracksRequestedColumns
,
tracksSelection
,
null
,
tracksSortOrder
);
tracksCursor
.
moveToFirst
();
if
(
tracksCursor
==
null
)
{
return
allTracks
;
}
while
(
tracksCursor
.
moveToNext
())
{
sendMusicFile
(
tracksCursor
.
getString
(
0
)
+
"||"
+
tracksCursor
.
getString
(
1
)
+
"||"
+
tracksCursor
.
getString
(
2
)
+
"||"
+
tracksCursor
.
getString
(
3
)
+
"||"
+
tracksCursor
.
getString
(
4
)
+
"||"
+
tracksCursor
.
getString
(
5
)
+
"||"
+
tracksCursor
.
getString
(
6
)
+
"||"
+
tracksCursor
.
getString
(
7
)
+
"||"
+
tracksCursor
.
getString
(
8
)
+
"||"
+
tracksCursor
.
getString
(
9
)
+
"||"
+
tracksCursor
.
getString
(
10
));
if
(!
tracksCursor
.
moveToFirst
())
{
return
allTracks
;
}
androidMusicScanTracksFinishing
();
do
{
AlbumDataType
currentAlbum
=
allAlbums
.
get
(
tracksCursor
.
getInt
(
5
));
androidMusicScanAlbumsStarting
(
);
int
trackAndDiscNumber
=
tracksCursor
.
getInt
(
7
);
//Some audio may be explicitly marked as not being music
/*String albumsSortOrder = MediaStore.Audio.Albums.DEFAULT_SORT_ORDER + " ASC";
allTracks
.
add
(
new
TrackDataType
(
tracksCursor
.
getString
(
1
),
tracksCursor
.
getString
(
2
),
tracksCursor
.
getString
(
4
),
tracksCursor
.
getString
(
6
),
trackAndDiscNumber
%
1000
,
trackAndDiscNumber
/
1000
,
tracksCursor
.
getLong
(
8
),
tracksCursor
.
getString
(
9
),
currentAlbum
.
getAlbumCover
(),
""
,
tracksCursor
.
getString
(
10
)));
}
while
(
tracksCursor
.
moveToNext
());
Cursor albumsCursor = ctx.getContentResolver().query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, albumsRequestedColumns, null, null, albumsSortOrder);
return
allTracks
;
}
}
albumsCursor.moveToFirst();
class
TrackDataType
{
TrackDataType
(
String
title
,
String
artist
,
String
albumName
,
String
albumArtist
,
int
trackNumber
,
int
discNumber
,
long
duration
,
String
resourceURI
,
String
albumCover
,
String
genre
,
String
composer
)
{
this
.
mTitle
=
title
;
this
.
mArtist
=
artist
;
this
.
mAlbumName
=
albumName
;
this
.
mAlbumArtist
=
albumArtist
;
this
.
mTrackNumber
=
trackNumber
;
this
.
mDiscNumber
=
discNumber
;
this
.
mDuration
=
duration
;
this
.
mResourceURI
=
resourceURI
;
this
.
mAlbumCover
=
albumCover
;
this
.
mGenre
=
genre
;
this
.
mComposer
=
composer
;
}
while(albumsCursor.moveToNext()) {
sendMusicAlbum(albumsCursor.getString(0) + "||" + albumsCursor.getString(1) + "||" +
albumsCursor.getString(2) + "||" + albumsCursor.getString(3) + "||" + albumsCursor.getString(4));
}*/
public
String
getTitle
()
{
return
mTitle
;
}
androidMusicScanAlbumsFinishing
();
public
String
getArtist
()
{
return
mArtist
;
}
private
static
native
void
androidMusicScanTracksStarting
();
public
String
getAlbumName
()
{
return
mAlbumName
;
}
public
String
getAlbumArtist
()
{
return
mAlbumArtist
;
}
public
int
getTrackNumber
()
{
return
mTrackNumber
;
}
public
int
getDiscNumber
()
{
return
mDiscNumber
;
}
public
long
getDuration
()
{
return
mDuration
;
}
public
String
getResourceURI
()
{
return
mResourceURI
;
}
public
String
getAlbumCover
()
{
return
mAlbumCover
;
}
public
String
getGenre
()
{
return
mGenre
;
}
public
String
getComposer
()
{
return
mComposer
;
}
private
String
mTitle
;
private
String
mArtist
;
private
String
mAlbumName
;
private
String
mAlbumArtist
;
private
int
mTrackNumber
;
private
int
mDiscNumber
;
private
long
mDuration
;
private
String
mResourceURI
;
private
String
mAlbumCover
;
private
String
mGenre
;
private
String
mComposer
;
}
private
static
native
void
sendMusicFile
(
String
musicFile
);
class
AlbumDataType
{
AlbumDataType
(
String
albumName
,
String
albumArtist
,
String
albumCover
)
{
this
.
mAlbumName
=
albumName
;
this
.
mAlbumArtist
=
albumArtist
;
this
.
mAlbumCover
=
albumCover
;
}
private
static
native
void
androidMusicScanTracksFinishing
();
public
String
getAlbumName
()
{
return
mAlbumName
;
}
private
static
native
void
androidMusicScanAlbumsStarting
();
public
String
getAlbumArtist
()
{
return
mAlbumArtist
;
}
private
static
native
void
sendMusicAlbum
(
String
musicFile
);
public
String
getAlbumCover
()
{
return
mAlbumCover
;
}
private
static
native
void
androidMusicScanAlbumsFinishing
();
private
String
mAlbumName
;
private
String
mAlbumArtist
;
private
String
mAlbumCover
;
}
src/android/androidfilelisting.cpp
View file @
9c36a835
...
...
@@ -33,48 +33,6 @@
#include <algorithm>
#include <memory>
static
void
tracksAndroidScanStarted
(
JNIEnv
*/
*
env
*/
,
jobject
/*obj*/
)
{
AndroidFileListing
::
currentInstance
()
->
androidMusicTracksScanStarted
();
}
static
void
sentMusicFile
(
JNIEnv
*/
*
env
*/
,
jobject
/*obj*/
,
jstring
musicFile
)
{
QAndroidJniObject
musicFileJavaString
(
musicFile
);
AndroidFileListing
::
currentInstance
()
->
newMusicTrack
(
musicFileJavaString
.
toString
());
}
static
void
tracksAndroidScanFinished
(
JNIEnv
*/
*
env
*/
,
jobject
/*obj*/
)
{
AndroidFileListing
::
currentInstance
()
->
androidMusicTracksScanFinished
();
}
static
void
albumsAndroidScanStarted
(
JNIEnv
*/
*
env
*/
,
jobject
/*obj*/
)
{
AndroidFileListing
::
currentInstance
()
->
androidMusicAlbumsScanStarted
();
}
static
void
sentMusicAlbum
(
JNIEnv
*/
*
env
*/
,
jobject
/*obj*/
,
jstring
musicAlbum
)
{
QAndroidJniObject
musicAlbumJavaString
(
musicAlbum
);
AndroidFileListing
::
currentInstance
()
->
newMusicAlbum
(
musicAlbumJavaString
.
toString
());
}
static
void
albumsAndroidScanFinished
(
JNIEnv
*/
*
env
*/
,
jobject
/*obj*/
)
{
AndroidFileListing
::
currentInstance
()
->
androidMusicAlbumsScanFinished
();
}
static
void
readExternalStoragePermissionIsOk
(
JNIEnv
*/
*
env
*/
,
jobject
/*obj*/
)
{
AndroidFileListing
::
currentInstance
()
->
readExternalStoragePermissionIsOk
();
}
static
void
readExternalStoragePermissionIsKo
(
JNIEnv
*/
*
env
*/
,
jobject
/*obj*/
)
{
AndroidFileListing
::
currentInstance
()
->
readExternalStoragePermissionIsKo
();
}
class
AndroidFileListingPrivate
{
public:
...
...
@@ -114,105 +72,6 @@ AndroidFileListing *AndroidFileListing::currentInstance()
void
AndroidFileListing
::
registerNativeMethods
()
{
JNINativeMethod
methods
[]
{{
"androidMusicScanTracksStarting"
,
"()V"
,
reinterpret_cast
<
void
*>
(
tracksAndroidScanStarted
)},
{
"sendMusicFile"
,
"(Ljava/lang/String;)V"
,
reinterpret_cast
<
void
*>
(
sentMusicFile
)},
{
"androidMusicScanTracksFinishing"
,
"()V"
,
reinterpret_cast
<
void
*>
(
tracksAndroidScanFinished
)},
{
"androidMusicScanAlbumsStarting"
,
"()V"
,
reinterpret_cast
<
void
*>
(
albumsAndroidScanStarted
)},
{
"sendMusicAlbum"
,
"(Ljava/lang/String;)V"
,
reinterpret_cast
<
void
*>
(
sentMusicAlbum
)},
{
"androidMusicScanAlbumsFinishing"
,
"()V"
,
reinterpret_cast
<
void
*>
(
albumsAndroidScanFinished
)},
};
QAndroidJniObject
javaClass
(
"org/kde/elisa/ElisaActivity"
);
QAndroidJniEnvironment
env
;
jclass
objectClass
=
env
->
GetObjectClass
(
javaClass
.
object
<
jobject
>
());
env
->
RegisterNatives
(
objectClass
,
methods
,
sizeof
(
methods
)
/
sizeof
(
methods
[
0
]));
env
->
DeleteLocalRef
(
objectClass
);
}
void
AndroidFileListing
::
readExternalStoragePermissionIsOk
()
{
}
void
AndroidFileListing
::
readExternalStoragePermissionIsKo
()
{
qCInfo
(
orgKdeElisaAndroid
())
<<
"AndroidFileListing::readExternalStoragePermissionIsKo"
;
}
void
AndroidFileListing
::
androidMusicTracksScanStarted
()
{
qCInfo
(
orgKdeElisaAndroid
())
<<
"AndroidFileListing::androidMusicTracksScanStarted"
;
}
void
AndroidFileListing
::
newMusicTrack
(
const
QString
&
trackDescription
)
{
qCInfo
(
orgKdeElisaAndroid
())
<<
"AndroidFileListing::newMusicTrack"
<<
trackDescription
;
auto
trackData
=
trackDescription
.
split
(
QStringLiteral
(
"||"
));
auto
newTrack
=
DataTypes
::
TrackDataType
{};
newTrack
[
DataTypes
::
TitleRole
]
=
trackData
[
1
];
bool
conversionOK
=
false
;
if
(
trackData
[
2
]
!=
QLatin1String
(
"null"
))
{
newTrack
[
DataTypes
::
TrackNumberRole
]
=
trackData
[
2
].
toInt
(
&
conversionOK
);
if
(
!
conversionOK
)
{
qInfo
()
<<
"newMusicTrack"
<<
trackData
[
1
]
<<
trackData
[
2
];
}
}
if
(
trackData
[
3
]
!=
QLatin1String
(
"null"
))
{
newTrack
[
DataTypes
::
YearRole
]
=
trackData
[
3
].
toInt
(
&
conversionOK
);
if
(
!
conversionOK
)
{
qInfo
()
<<
"newMusicTrack"
<<
trackData
[
1
]
<<
trackData
[
3
];
}
}
if
(
trackData
[
4
]
!=
QLatin1String
(
"null"
))
{
newTrack
[
DataTypes
::
DurationRole
]
=
QTime
::
fromMSecsSinceStartOfDay
(
trackData
[
4
].
toInt
());
}
else
{
newTrack
[
DataTypes
::
DurationRole
]
=
QTime
::
fromMSecsSinceStartOfDay
(
1
);
}
newTrack
[
DataTypes
::
ResourceRole
]
=
QUrl
::
fromLocalFile
(
trackData
[
5
]);
newTrack
[
DataTypes
::
ArtistRole
]
=
trackData
[
6
];
newTrack
[
DataTypes
::
AlbumRole
]
=
trackData
[
8
];
newTrack
[
DataTypes
::
ComposerRole
]
=
trackData
[
10
];
QFileInfo
scanFileInfo
(
trackData
[
5
]);
newTrack
[
DataTypes
::
FileModificationTime
]
=
scanFileInfo
.
metadataChangeTime
();
newTrack
[
DataTypes
::
RatingRole
]
=
0
;
newTrack
[
DataTypes
::
ElementTypeRole
]
=
ElisaUtils
::
Track
;
qCInfo
(
orgKdeElisaAndroid
())
<<
"AndroidFileListing::newMusicTrack"
<<
newTrack
;
d
->
mNewTracks
.
push_back
(
newTrack
);
}
void
AndroidFileListing
::
androidMusicTracksScanFinished
()
{
qCInfo
(
orgKdeElisaAndroid
())
<<
"AndroidFileListing::androidMusicTracksScanFinished"
;
}
void
AndroidFileListing
::
androidMusicAlbumsScanStarted
()
{
qCInfo
(
orgKdeElisaAndroid
())
<<
"AndroidFileListing::androidMusicAlbumsScanStarted"
;
}
void
AndroidFileListing
::
newMusicAlbum
(
const
QString
&
albumDescription
)
{
auto
albumData
=
albumDescription
.
split
(
QStringLiteral
(
"||"
));
if
(
albumData
[
2
]
!=
QLatin1String
(
"null)"
))
{
d
->
mCovers
[
albumData
[
1
]]
=
QUrl
::
fromLocalFile
(
albumData
[
2
]);
}
}
void
AndroidFileListing
::
androidMusicAlbumsScanFinished
()
{
qCInfo
(
orgKdeElisaAndroid
())
<<
"AndroidFileListing::androidMusicAlbumsScanFinished"
;
Q_EMIT
tracksList
(
d
->
mNewTracks
,
d
->
mCovers
);
Q_EMIT
indexingFinished
();
}
void
AndroidFileListing
::
executeInit
(
QHash
<
QUrl
,
QDateTime
>
allFiles
)
...
...
@@ -237,10 +96,44 @@ void AndroidFileListing::triggerRefreshOfContent()
AbstractFileListing
::
triggerRefreshOfContent
();
QAndroidJniObject
::
callStaticMethod
<
void
>
(
"org/kde/elisa/ElisaActivity"
,
QAndroidJniObject
musicList
=
QAndroidJniObject
::
callStaticObjectMethod
(
"org/kde/elisa/ElisaActivity"
,
"listAudioFiles"
,
"(Landroid/content/Context;)
V
"
,
"(Landroid/content/Context;)
Ljava/util/ArrayList;
"
,
QtAndroid
::
androidContext
().
object
());
auto
nbTracks
=
musicList
.
callMethod
<
jint
>
(
"size"
);
for
(
int
i
=
0
;
i
<
nbTracks
;
++
i
)
{
auto
newTrack
=
DataTypes
::
TrackDataType
{};
auto
track
=
musicList
.
callObjectMethod
(
"get"
,
"(I)Ljava/lang/Object;"
,
i
);
newTrack
[
DataTypes
::
TitleRole
]
=
track
.
callObjectMethod
(
"getTitle"
,
"()Ljava/lang/String;"
).
toString
();
newTrack
[
DataTypes
::
ArtistRole
]
=
track
.
callObjectMethod
(
"getArtist"
,
"()Ljava/lang/String;"
).
toString
();
newTrack
[
DataTypes
::
AlbumRole
]
=
track
.
callObjectMethod
(
"getAlbumName"
,
"()Ljava/lang/String;"
).
toString
();
newTrack
[
DataTypes
::
AlbumArtistRole
]
=
track
.
callObjectMethod
(
"getAlbumArtist"
,
"()Ljava/lang/String;"
).
toString
();
newTrack
[
DataTypes
::
TrackNumberRole
]
=
track
.
callMethod
<
jint
>
(
"getTrackNumber"
);
newTrack
[
DataTypes
::
DiscNumberRole
]
=
track
.
callMethod
<
jint
>
(
"getDiscNumber"
);
newTrack
[
DataTypes
::
DurationRole
]
=
QTime
::
fromMSecsSinceStartOfDay
(
track
.
callMethod
<
jlong
>
(
"getDuration"
));
newTrack
[
DataTypes
::
ResourceRole
]
=
QUrl
::
fromLocalFile
(
track
.
callObjectMethod
(
"getResourceURI"
,
"()Ljava/lang/String;"
).
toString
());
newTrack
[
DataTypes
::
ImageUrlRole
]
=
QUrl
::
fromLocalFile
(
track
.
callObjectMethod
(
"getAlbumCover"
,
"()Ljava/lang/String;"
).
toString
());
newTrack
[
DataTypes
::
GenreRole
]
=
track
.
callObjectMethod
(
"getGenre"
,
"()Ljava/lang/String;"
).
toString
();
newTrack
[
DataTypes
::
ComposerRole
]
=
track
.
callObjectMethod
(
"getComposer"
,
"()Ljava/lang/String;"
).
toString
();
QFileInfo
scanFileInfo
(
newTrack
.
resourceURI
().
toLocalFile
());
newTrack
[
DataTypes
::
FileModificationTime
]
=
scanFileInfo
.
metadataChangeTime
();
newTrack
[
DataTypes
::
RatingRole
]
=
0
;
newTrack
[
DataTypes
::
ElementTypeRole
]
=
ElisaUtils
::
Track
;
qCInfo
(
orgKdeElisaAndroid
())
<<
"AndroidFileListing::triggerRefreshOfContent"
<<
newTrack
;
d
->
mNewTracks
.
push_back
(
newTrack
);
}
Q_EMIT
tracksList
(
d
->
mNewTracks
,
d
->
mCovers
);
Q_EMIT
indexingFinished
();
}
DataTypes
::
TrackDataType
AndroidFileListing
::
scanOneFile
(
const
QUrl
&
scanFile
,
const
QFileInfo
&
scanFileInfo
,
FileSystemWatchingModes
watchForFileSystemChanges
)
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment