این وبسایت یک نسخه آزمایشی از سایت developer android می‌باشد که در حال ترجمه می‌باشد.
برای پیوستن به تیم ترجمه و خواندن مستندات اینجا کلیک کنید.
MediaBrowserService / src / com.example.android.mediabrowserservice / model /

MusicProvider.java

1
/*
2
 * Copyright (C) 2014 The Android Open Source Project
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
 
17
package com.example.android.mediabrowserservice.model;
18
 
19
import android.media.MediaMetadata;
20
import android.os.AsyncTask;
21
 
22
import com.example.android.mediabrowserservice.utils.LogHelper;
23
 
24
import org.json.JSONArray;
25
import org.json.JSONException;
26
import org.json.JSONObject;
27
 
28
import java.io.BufferedInputStream;
29
import java.io.BufferedReader;
30
import java.io.IOException;
31
import java.io.InputStream;
32
import java.io.InputStreamReader;
33
import java.net.URLConnection;
34
import java.util.ArrayList;
35
import java.util.HashMap;
36
import java.util.HashSet;
37
import java.util.List;
38
import java.util.concurrent.locks.ReentrantLock;
39
 
40
/**
41
 * Utility class to get a list of MusicTrack's based on a server-side JSON
42
 * configuration.
43
 */
44
public class MusicProvider {
45
 
46
    private static final String TAG = "MusicProvider";
47
 
48
    private static final String CATALOG_URL = "http://storage.googleapis.com/automotive-media/music.json";
49
 
50
    public static final String CUSTOM_METADATA_TRACK_SOURCE = "__SOURCE__";
51
 
52
    private static String JSON_MUSIC = "music";
53
    private static String JSON_TITLE = "title";
54
    private static String JSON_ALBUM = "album";
55
    private static String JSON_ARTIST = "artist";
56
    private static String JSON_GENRE = "genre";
57
    private static String JSON_SOURCE = "source";
58
    private static String JSON_IMAGE = "image";
59
    private static String JSON_TRACK_NUMBER = "trackNumber";
60
    private static String JSON_TOTAL_TRACK_COUNT = "totalTrackCount";
61
    private static String JSON_DURATION = "duration";
62
 
63
    private final ReentrantLock initializationLock = new ReentrantLock();
64
 
65
    // Categorized caches for music track data:
66
    private final HashMap<String, List<MediaMetadata>> mMusicListByGenre;
67
    private final HashMap<String, MediaMetadata> mMusicListById;
68
 
69
    private final HashSet<String> mFavoriteTracks;
70
 
71
    enum State {
72
        NON_INITIALIZED, INITIALIZING, INITIALIZED;
73
    }
74
 
75
    private State mCurrentState = State.NON_INITIALIZED;
76
 
77
 
78
    public interface Callback {
79
        void onMusicCatalogReady(boolean success);
80
    }
81
 
82
    public MusicProvider() {
83
        mMusicListByGenre = new HashMap<>();
84
        mMusicListById = new HashMap<>();
85
        mFavoriteTracks = new HashSet<>();
86
    }
87
 
88
    /**
89
     * Get an iterator over the list of genres
90
     *
91
     * @return
92
     */
93
    public Iterable<String> getGenres() {
94
        if (mCurrentState != State.INITIALIZED) {
95
            return new ArrayList<String>(0);
96
        }
97
        return mMusicListByGenre.keySet();
98
    }
99
 
100
    /**
101
     * Get music tracks of the given genre
102
     *
103
     * @return
104
     */
105
    public Iterable<MediaMetadata> getMusicsByGenre(String genre) {
106
        if (mCurrentState != State.INITIALIZED || !mMusicListByGenre.containsKey(genre)) {
107
            return new ArrayList<MediaMetadata>();
108
        }
109
        return mMusicListByGenre.get(genre);
110
    }
111
 
112
    /**
113
     * Very basic implementation of a search that filter music tracks which title containing
114
     * the given query.
115
     *
116
     * @return
117
     */
118
    public Iterable<MediaMetadata> searchMusics(String titleQuery) {
119
        ArrayList<MediaMetadata> result = new ArrayList<>();
120
        if (mCurrentState != State.INITIALIZED) {
121
            return result;
122
        }
123
        titleQuery = titleQuery.toLowerCase();
124
        for (MediaMetadata track: mMusicListById.values()) {
125
            if (track.getString(MediaMetadata.METADATA_KEY_TITLE).toLowerCase()
126
                    .contains(titleQuery)) {
127
                result.add(track);
128
            }
129
        }
130
        return result;
131
    }
132
 
133
    public MediaMetadata getMusic(String mediaId) {
134
        return mMusicListById.get(mediaId);
135
    }
136
 
137
    public void setFavorite(String mediaId, boolean favorite) {
138
        if (favorite) {
139
            mFavoriteTracks.add(mediaId);
140
        } else {
141
            mFavoriteTracks.remove(mediaId);
142
        }
143
    }
144
 
145
    public boolean isFavorite(String musicId) {
146
        return mFavoriteTracks.contains(musicId);
147
    }
148
 
149
    public boolean isInitialized() {
150
        return mCurrentState == State.INITIALIZED;
151
    }
152
 
153
    /**
154
     * Get the list of music tracks from a server and caches the track information
155
     * for future reference, keying tracks by mediaId and grouping by genre.
156
     *
157
     * @return
158
     */
159
    public void retrieveMedia(final Callback callback) {
160
 
161
        if (mCurrentState == State.INITIALIZED) {
162
            // Nothing to do, execute callback immediately
163
            callback.onMusicCatalogReady(true);
164
            return;
165
        }
166
 
167
        // Asynchronously load the music catalog in a separate thread
168
        new AsyncTask() {
169
            @Override
170
            protected Object doInBackground(Object[] objects) {
171
                retrieveMediaAsync(callback);
172
                return null;
173
            }
174
        }.execute();
175
    }
176
 
177
    private void retrieveMediaAsync(Callback callback) {
178
        initializationLock.lock();
179
 
180
        try {
181
            if (mCurrentState == State.NON_INITIALIZED) {
182
                mCurrentState = State.INITIALIZING;
183
 
184
                int slashPos = CATALOG_URL.lastIndexOf('/');
185
                String path = CATALOG_URL.substring(0, slashPos + 1);
186
                JSONObject jsonObj = parseUrl(CATALOG_URL);
187
 
188
                JSONArray tracks = jsonObj.getJSONArray(JSON_MUSIC);
189
                if (tracks != null) {
190
                    for (int j = 0; j < tracks.length(); j++) {
191
                        MediaMetadata item = buildFromJSON(tracks.getJSONObject(j), path);
192
                        String genre = item.getString(MediaMetadata.METADATA_KEY_GENRE);
193
                        List<MediaMetadata> list = mMusicListByGenre.get(genre);
194
                        if (list == null) {
195
                            list = new ArrayList<>();
196
                        }
197
                        list.add(item);
198
                        mMusicListByGenre.put(genre, list);
199
                        mMusicListById.put(item.getString(MediaMetadata.METADATA_KEY_MEDIA_ID),
200
                                item);
201
                    }
202
                }
203
                mCurrentState = State.INITIALIZED;
204
            }
205
        } catch (RuntimeException | JSONException e) {
206
            LogHelper.e(TAG, e, "Could not retrieve music list");
207
        } finally {
208
            if (mCurrentState != State.INITIALIZED) {
209
                // Something bad happened, so we reset state to NON_INITIALIZED to allow
210
                // retries (eg if the network connection is temporary unavailable)
211
                mCurrentState = State.NON_INITIALIZED;
212
            }
213
            initializationLock.unlock();
214
            if (callback != null) {
215
                callback.onMusicCatalogReady(mCurrentState == State.INITIALIZED);
216
            }
217
        }
218
    }
219
 
220
    private MediaMetadata buildFromJSON(JSONObject json, String basePath) throws JSONException {
221
        String title = json.getString(JSON_TITLE);
222
        String album = json.getString(JSON_ALBUM);
223
        String artist = json.getString(JSON_ARTIST);
224
        String genre = json.getString(JSON_GENRE);
225
        String source = json.getString(JSON_SOURCE);
226
        String iconUrl = json.getString(JSON_IMAGE);
227
        int trackNumber = json.getInt(JSON_TRACK_NUMBER);
228
        int totalTrackCount = json.getInt(JSON_TOTAL_TRACK_COUNT);
229
        int duration = json.getInt(JSON_DURATION) * 1000; // ms
230
 
231
        LogHelper.d(TAG, "Found music track: ", json);
232
 
233
        // Media is stored relative to JSON file
234
        if (!source.startsWith("http")) {
235
            source = basePath + source;
236
        }
237
        if (!iconUrl.startsWith("http")) {
238
            iconUrl = basePath + iconUrl;
239
        }
240
        // Since we don't have a unique ID in the server, we fake one using the hashcode of
241
        // the music source. In a real world app, this could come from the server.
242
        String id = String.valueOf(source.hashCode());
243
 
244
        // Adding the music source to the MediaMetadata (and consequently using it in the
245
        // mediaSession.setMetadata) is not a good idea for a real world music app, because
246
        // the session metadata can be accessed by notification listeners. This is done in this
247
        // sample for convenience only.
248
        return new MediaMetadata.Builder()
249
                .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, id)
250
                .putString(CUSTOM_METADATA_TRACK_SOURCE, source)
251
                .putString(MediaMetadata.METADATA_KEY_ALBUM, album)
252
                .putString(MediaMetadata.METADATA_KEY_ARTIST, artist)
253
                .putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
254
                .putString(MediaMetadata.METADATA_KEY_GENRE, genre)
255
                .putString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI, iconUrl)
256
                .putString(MediaMetadata.METADATA_KEY_TITLE, title)
257
                .putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, trackNumber)
258
                .putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, totalTrackCount)
259
                .build();
260
    }
261
 
262
    /**
263
     * Download a JSON file from a server, parse the content and return the JSON
264
     * object.
265
     *
266
     * @param urlString
267
     * @return
268
     */
269
    private JSONObject parseUrl(String urlString) {
270
        InputStream is = null;
271
        try {
272
            java.net.URL url = new java.net.URL(urlString);
273
            URLConnection urlConnection = url.openConnection();
274
            is = new BufferedInputStream(urlConnection.getInputStream());
275
            BufferedReader reader = new BufferedReader(new InputStreamReader(
276
                    urlConnection.getInputStream(), "iso-8859-1"));
277
            StringBuilder sb = new StringBuilder();
278
            String line = null;
279
            while ((line = reader.readLine()) != null) {
280
                sb.append(line);
281
            }
282
            return new JSONObject(sb.toString());
283
        } catch (Exception e) {
284
            LogHelper.e(TAG, "Failed to parse the json for media list", e);
285
            return null;
286
        } finally {
287
            if (is != null) {
288
                try {
289
                    is.close();