Skip to content

Commit 0b71839

Browse files
committed
Further speed up playlist processing
Previously when processing playlists, the entire audio table would be read for every line in a playlist file. While there would be only one query, a lot of data was being moved from sqlite to java over and over again, and if the data didn't all fit in a CursorWindow, additional queries would be done under the hood. With this change, playlists are first cached in memory. Then the audio table is queried, and for every row in the audio table, the best match from the playlist cache is found. This way the audio table is only traversed once, so each row is only fetched once. Once the entire audio table has been read, the in-memory playlist cache contains the best matching entry for each line, and the playlists are written out to the database. Currently, the audio table is traversed once for each playlist. This could be further optimized in the future by processing all playlists at the same time. b/6346786 Change-Id: Iead3f9ae838d600d085e8e6d3c4874d42314468e
1 parent d7a2a42 commit 0b71839

File tree

1 file changed

+85
-96
lines changed

1 file changed

+85
-96
lines changed

media/java/android/media/MediaScanner.java

Lines changed: 85 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,16 @@
3131
import android.mtp.MtpConstants;
3232
import android.net.Uri;
3333
import android.os.Environment;
34-
import android.os.Process;
3534
import android.os.RemoteException;
3635
import android.os.SystemProperties;
3736
import android.provider.MediaStore;
38-
import android.provider.MediaStore.Files.FileColumns;
39-
import android.provider.Settings;
4037
import android.provider.MediaStore.Audio;
38+
import android.provider.MediaStore.Audio.Playlists;
4139
import android.provider.MediaStore.Files;
40+
import android.provider.MediaStore.Files.FileColumns;
4241
import android.provider.MediaStore.Images;
4342
import android.provider.MediaStore.Video;
44-
import android.provider.MediaStore.Audio.Playlists;
43+
import android.provider.Settings;
4544
import android.sax.Element;
4645
import android.sax.ElementListener;
4746
import android.sax.RootElement;
@@ -56,10 +55,8 @@
5655
import java.io.IOException;
5756
import java.io.InputStreamReader;
5857
import java.util.ArrayList;
59-
import java.util.HashMap;
6058
import java.util.HashSet;
6159
import java.util.Iterator;
62-
import java.util.LinkedHashMap;
6360
import java.util.Locale;
6461

6562
import libcore.io.ErrnoException;
@@ -372,6 +369,14 @@ public String toString() {
372369
}
373370
}
374371

372+
private static class PlaylistEntry {
373+
String path;
374+
long bestmatchid;
375+
int bestmatchlevel;
376+
}
377+
378+
private ArrayList<PlaylistEntry> mPlaylistEntries = new ArrayList<PlaylistEntry>();
379+
375380
private MediaInserter mMediaInserter;
376381

377382
private ArrayList<FileEntry> mPlayLists;
@@ -1492,93 +1497,83 @@ private int matchPaths(String path1, String path2) {
14921497
return result;
14931498
}
14941499

1495-
private boolean addPlayListEntry(String entry, String playListDirectory,
1496-
Uri uri, ContentValues values, int index, Cursor fileList) {
1500+
private boolean matchEntries(long rowId, String data) {
1501+
1502+
int len = mPlaylistEntries.size();
1503+
boolean done = true;
1504+
for (int i = 0; i < len; i++) {
1505+
PlaylistEntry entry = mPlaylistEntries.get(i);
1506+
if (entry.bestmatchlevel == Integer.MAX_VALUE) {
1507+
continue; // this entry has been matched already
1508+
}
1509+
done = false;
1510+
if (data.equalsIgnoreCase(entry.path)) {
1511+
entry.bestmatchid = rowId;
1512+
entry.bestmatchlevel = Integer.MAX_VALUE;
1513+
continue; // no need for path matching
1514+
}
1515+
1516+
int matchLength = matchPaths(data, entry.path);
1517+
if (matchLength > entry.bestmatchlevel) {
1518+
entry.bestmatchid = rowId;
1519+
entry.bestmatchlevel = matchLength;
1520+
}
1521+
}
1522+
return done;
1523+
}
14971524

1525+
private void cachePlaylistEntry(String line, String playListDirectory) {
1526+
PlaylistEntry entry = new PlaylistEntry();
14981527
// watch for trailing whitespace
1499-
int entryLength = entry.length();
1500-
while (entryLength > 0 && Character.isWhitespace(entry.charAt(entryLength - 1))) entryLength--;
1528+
int entryLength = line.length();
1529+
while (entryLength > 0 && Character.isWhitespace(line.charAt(entryLength - 1))) entryLength--;
15011530
// path should be longer than 3 characters.
15021531
// avoid index out of bounds errors below by returning here.
1503-
if (entryLength < 3) return false;
1504-
if (entryLength < entry.length()) entry = entry.substring(0, entryLength);
1532+
if (entryLength < 3) return;
1533+
if (entryLength < line.length()) line = line.substring(0, entryLength);
15051534

15061535
// does entry appear to be an absolute path?
15071536
// look for Unix or DOS absolute paths
1508-
char ch1 = entry.charAt(0);
1537+
char ch1 = line.charAt(0);
15091538
boolean fullPath = (ch1 == '/' ||
1510-
(Character.isLetter(ch1) && entry.charAt(1) == ':' && entry.charAt(2) == '\\'));
1539+
(Character.isLetter(ch1) && line.charAt(1) == ':' && line.charAt(2) == '\\'));
15111540
// if we have a relative path, combine entry with playListDirectory
15121541
if (!fullPath)
1513-
entry = playListDirectory + entry;
1514-
1542+
line = playListDirectory + line;
1543+
entry.path = line;
15151544
//FIXME - should we look for "../" within the path?
15161545

1517-
// best matching MediaFile for the play list entry
1518-
FileEntry bestMatch = null;
1519-
1520-
// number of rightmost file/directory names for bestMatch
1521-
int bestMatchLength = 0;
1522-
1523-
if (fileList != null) {
1524-
int count = fileList.getCount();
1525-
// Backing up a little in the cursor helps when the files in the
1526-
// playlist are not in the same order as they are in the database
1527-
// but are still close.
1528-
fileList.move(-1000);
1529-
while(--count >= 0) {
1530-
if (!fileList.moveToNext()) {
1531-
fileList.moveToFirst();
1532-
}
1533-
long rowId = fileList.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
1534-
String path = fileList.getString(FILES_PRESCAN_PATH_COLUMN_INDEX);
1535-
int format = fileList.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX);
1536-
long lastModified = fileList.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX);
1537-
1538-
if (path.equalsIgnoreCase(entry)) {
1539-
bestMatch = new FileEntry(rowId, path, lastModified, format);
1540-
break; // don't bother continuing search
1541-
}
1546+
mPlaylistEntries.add(entry);
1547+
}
15421548

1543-
int matchLength = matchPaths(path, entry);
1544-
if (matchLength > bestMatchLength) {
1545-
bestMatch = new FileEntry(rowId, path, lastModified, format);
1546-
bestMatchLength = matchLength;
1547-
}
1549+
private void processCachedPlaylist(Cursor fileList, ContentValues values, Uri playlistUri) {
1550+
fileList.moveToPosition(-1);
1551+
while (fileList.moveToNext()) {
1552+
long rowId = fileList.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
1553+
String data = fileList.getString(FILES_PRESCAN_PATH_COLUMN_INDEX);
1554+
if (matchEntries(rowId, data)) {
1555+
break;
15481556
}
15491557
}
15501558

1551-
if (bestMatch == null) {
1552-
return false;
1553-
}
1554-
1555-
try {
1556-
// check rowid is set. Rowid may be missing if it is inserted by bulkInsert().
1557-
if (bestMatch.mRowId == 0) {
1558-
Cursor c = mMediaProvider.query(mAudioUri, ID_PROJECTION,
1559-
MediaStore.Files.FileColumns.DATA + "=?",
1560-
new String[] { bestMatch.mPath }, null, null);
1561-
if (c != null) {
1562-
if (c.moveToNext()) {
1563-
bestMatch.mRowId = c.getLong(0);
1564-
}
1565-
c.close();
1566-
}
1567-
if (bestMatch.mRowId == 0) {
1568-
return false;
1559+
int len = mPlaylistEntries.size();
1560+
int index = 0;
1561+
for (int i = 0; i < len; i++) {
1562+
PlaylistEntry entry = mPlaylistEntries.get(i);
1563+
if (entry.bestmatchlevel > 0) {
1564+
try {
1565+
values.clear();
1566+
values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, Integer.valueOf(index));
1567+
values.put(MediaStore.Audio.Playlists.Members.AUDIO_ID, Long.valueOf(entry.bestmatchid));
1568+
mMediaProvider.insert(playlistUri, values);
1569+
index++;
1570+
} catch (RemoteException e) {
1571+
Log.e(TAG, "RemoteException in MediaScanner.processCachedPlaylist()", e);
1572+
return;
15691573
}
15701574
}
1571-
// OK, now we are ready to add this to the database
1572-
values.clear();
1573-
values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, Integer.valueOf(index));
1574-
values.put(MediaStore.Audio.Playlists.Members.AUDIO_ID, Long.valueOf(bestMatch.mRowId));
1575-
mMediaProvider.insert(uri, values);
1576-
} catch (RemoteException e) {
1577-
Log.e(TAG, "RemoteException in MediaScanner.addPlayListEntry()", e);
1578-
return false;
15791575
}
1580-
1581-
return true;
1576+
mPlaylistEntries.clear();
15821577
}
15831578

15841579
private void processM3uPlayList(String path, String playListDirectory, Uri uri,
@@ -1590,16 +1585,16 @@ private void processM3uPlayList(String path, String playListDirectory, Uri uri,
15901585
reader = new BufferedReader(
15911586
new InputStreamReader(new FileInputStream(f)), 8192);
15921587
String line = reader.readLine();
1593-
int index = 0;
1588+
mPlaylistEntries.clear();
15941589
while (line != null) {
15951590
// ignore comment lines, which begin with '#'
15961591
if (line.length() > 0 && line.charAt(0) != '#') {
1597-
values.clear();
1598-
if (addPlayListEntry(line, playListDirectory, uri, values, index, fileList))
1599-
index++;
1592+
cachePlaylistEntry(line, playListDirectory);
16001593
}
16011594
line = reader.readLine();
16021595
}
1596+
1597+
processCachedPlaylist(fileList, values, uri);
16031598
}
16041599
} catch (IOException e) {
16051600
Log.e(TAG, "IOException in MediaScanner.processM3uPlayList()", e);
@@ -1622,20 +1617,19 @@ private void processPlsPlayList(String path, String playListDirectory, Uri uri,
16221617
reader = new BufferedReader(
16231618
new InputStreamReader(new FileInputStream(f)), 8192);
16241619
String line = reader.readLine();
1625-
int index = 0;
1620+
mPlaylistEntries.clear();
16261621
while (line != null) {
16271622
// ignore comment lines, which begin with '#'
16281623
if (line.startsWith("File")) {
16291624
int equals = line.indexOf('=');
16301625
if (equals > 0) {
1631-
values.clear();
1632-
if (addPlayListEntry(line.substring(equals + 1), playListDirectory,
1633-
uri, values, index, fileList))
1634-
index++;
1626+
cachePlaylistEntry(line, playListDirectory);
16351627
}
16361628
}
16371629
line = reader.readLine();
16381630
}
1631+
1632+
processCachedPlaylist(fileList, values, uri);
16391633
}
16401634
} catch (IOException e) {
16411635
Log.e(TAG, "IOException in MediaScanner.processPlsPlayList()", e);
@@ -1653,15 +1647,9 @@ class WplHandler implements ElementListener {
16531647

16541648
final ContentHandler handler;
16551649
String playListDirectory;
1656-
Uri uri;
1657-
Cursor fileList;
1658-
ContentValues values = new ContentValues();
1659-
int index = 0;
16601650

16611651
public WplHandler(String playListDirectory, Uri uri, Cursor fileList) {
16621652
this.playListDirectory = playListDirectory;
1663-
this.uri = uri;
1664-
this.fileList = fileList;
16651653

16661654
RootElement root = new RootElement("smil");
16671655
Element body = root.getChild("body");
@@ -1676,13 +1664,11 @@ public WplHandler(String playListDirectory, Uri uri, Cursor fileList) {
16761664
public void start(Attributes attributes) {
16771665
String path = attributes.getValue("", "src");
16781666
if (path != null) {
1679-
values.clear();
1680-
if (addPlayListEntry(path, playListDirectory, uri, values, index, fileList)) {
1681-
index++;
1682-
}
1667+
cachePlaylistEntry(path, playListDirectory);
16831668
}
16841669
}
16851670

1671+
@Override
16861672
public void end() {
16871673
}
16881674

@@ -1692,15 +1678,18 @@ ContentHandler getContentHandler() {
16921678
}
16931679

16941680
private void processWplPlayList(String path, String playListDirectory, Uri uri,
1695-
Cursor fileList) {
1681+
ContentValues values, Cursor fileList) {
16961682
FileInputStream fis = null;
16971683
try {
16981684
File f = new File(path);
16991685
if (f.exists()) {
17001686
fis = new FileInputStream(f);
17011687

1688+
mPlaylistEntries.clear();
17021689
Xml.parse(fis, Xml.findEncodingByName("UTF-8"),
17031690
new WplHandler(playListDirectory, uri, fileList).getContentHandler());
1691+
1692+
processCachedPlaylist(fileList, values, uri);
17041693
}
17051694
} catch (SAXException e) {
17061695
e.printStackTrace();
@@ -1762,7 +1751,7 @@ private void processPlayList(FileEntry entry, Cursor fileList) throws RemoteExce
17621751
} else if (fileType == MediaFile.FILE_TYPE_PLS) {
17631752
processPlsPlayList(path, playListDirectory, membersUri, values, fileList);
17641753
} else if (fileType == MediaFile.FILE_TYPE_WPL) {
1765-
processWplPlayList(path, playListDirectory, membersUri, fileList);
1754+
processWplPlayList(path, playListDirectory, membersUri, values, fileList);
17661755
}
17671756
}
17681757

@@ -1800,7 +1789,7 @@ private void processPlayLists() throws RemoteException {
18001789
private native final void native_finalize();
18011790

18021791
/**
1803-
* Releases resouces associated with this MediaScanner object.
1792+
* Releases resources associated with this MediaScanner object.
18041793
* It is considered good practice to call this method when
18051794
* one is done using the MediaScanner object. After this method
18061795
* is called, the MediaScanner object can no longer be used.

0 commit comments

Comments
 (0)