001package com.baidu.cloud.media.download;
002
003import java.io.File;
004import java.util.HashMap;
005import java.util.LinkedList;
006import java.util.Queue;
007import java.util.concurrent.ConcurrentHashMap;
008
009import org.json.JSONObject;
010
011import com.baidu.cloud.media.download.DownloadableVideoItem.DownloadStatus;
012
013import android.content.Context;
014import android.content.SharedPreferences;
015import android.util.Log;
016
017/**
018 * 下载管理器-总控 提供开启下载的入口、并可以管理下载
019 * 
020 * @author shunwei
021 *
022 */
023public class VideoDownloadManager {
024    private static final String TAG = "VideoDownloadManager";
025
026    private static final String DEFAULT_USER_NAME = "_unk@nown_##$ default $##_unk@nown_"; // 一般用户名不允许有空格
027    private static final String DEFAULT_USER_NAME_MD5 = "4f0fecb0c26217433bafbf2a0d595c4e"; // md5(DEFAULT_USER_NAME)
028
029    private String userName = DEFAULT_USER_NAME;
030
031    private String md5User = DEFAULT_USER_NAME_MD5;
032
033    // private boolean isUserInited = false;
034    protected static final String DOWNLOAD_FOLDER = "cyberplayer_download_videos";
035    protected static final String SP_NAME_PREFIX = "__cyberplayer_dl_";
036
037    private static int maxDownloadingSum = 5;
038
039    private volatile int nowDownloadingSum = 0;
040    // use offer to add, use poll to fetch
041    private Queue<String> queueList = new LinkedList<String>();
042
043    private Context appContext;
044
045    private String customizedPlayerId;
046
047    private VideoDownloadManager(Context context, String userName) {
048        appContext = context.getApplicationContext();
049        this.userName = userName;
050        this.md5User = DownloadUtils.getMD5(userName);
051        restoreFromSharedPref();
052
053    }
054
055    private ConcurrentHashMap<String, HLSDownloadableItem> downloadMap
056            = new ConcurrentHashMap<String, HLSDownloadableItem>();
057
058    private static volatile VideoDownloadManager downloaderManager;
059    private static volatile String globalUserName;
060
061    /**
062     * 获取下载器单例
063     * @param context
064     * @param userName 用户标识
065     * @return
066     */
067    public static synchronized VideoDownloadManager getInstance(Context context, String userName) {
068        if (userName == null || userName.equals("")) {
069            Log.e(TAG, "getInstance failed, userName is null or empty.");
070            return null;
071        }
072        if (!userName.equals(globalUserName)) {
073            // change user
074            globalUserName = userName;
075            if (downloaderManager != null) {
076                downloaderManager.stopAll();
077                downloaderManager = null;
078            }
079        }
080        if (downloaderManager == null) {
081            downloaderManager = new VideoDownloadManager(context, userName);
082        }
083        return downloaderManager;
084    }
085
086    public void setCustomizedPlayerId(String playerId) {
087        customizedPlayerId = playerId;
088    }
089
090    protected String getCustomizedPlayerId() {
091        return customizedPlayerId;
092    }
093    
094    /**
095     * inner use.
096     * @return
097     */
098    protected static VideoDownloadManager getInstanceForInner() {
099        return downloaderManager;
100    }
101
102    /**
103     * 条件:在客户端切换用户时,不同用户的下载数据不共享,使用该接口; 时机:推荐在程序启动时,检查到“用户已登录”时调用 或在 用户登录成功时调用;
104     * 作用:初始化该用户之前已下载的数据; 备注:用同一个用户名,多次调用该接口,仅第一次调用有效;
105     * 
106     * @param userName
107     */
108    // public void initUserBlock(String userName) {
109    // if (userName == null || userName.equals("")) {
110    // Log.e(TAG, "initUser failed, userName is null or empty.");
111    // return;
112    // }
113    // if (!this.userName.equals(userName)) {
114    // // isUserInited = true;
115    // // old user-tasks will be removed
116    // this.userName = userName;
117    // this.md5User = DownloadUtils.getMD5(userName);
118    // // stop each downloader in downloadMap
119    // try {
120    // for (HashMap.Entry<String, HLSVideoDownloader> entry :
121    // downloadMap.entrySet()) {
122    // entry.getValue().pause();
123    // }
124    // } catch (Exception e) {
125    // Log.d(TAG, "" + e.getMessage());
126    // }
127    //
128    // restoreFromSharedPref();
129    // }
130    // }
131
132    /**
133     * 时机:用户登出事件发生时,调用该接口 作用:仅暂停所有目前的下载, 不会清除存储卡中的数据
134     *
135     */
136    public void stopAll() {
137        // stop each downloader in downloadMap
138        try {
139            // 赋予一个不存在的名字,以便比对
140            for (HashMap.Entry<String, HLSDownloadableItem> entry : downloadMap.entrySet()) {
141                entry.getValue().pause();
142            }
143        } catch (Exception e) {
144            Log.d(TAG, "" + e.getMessage());
145        }
146    }
147
148    // private boolean checkSettings() {
149    // if (!isUserInited) {
150    // Log.e(TAG, "Error: should invoke initUser first");
151    // return false;
152    // }
153    // return true;
154    // }
155
156    /**
157     * file: __cyberplayer_dl_md5(user) [ key:md5(url),value:{url:"", status:1,
158     * progress:3205, fileFolder:"", fileName:"", ts:} ]
159     */
160    private void restoreFromSharedPref() {
161        downloadMap.clear();
162        // read pref
163        try {
164            SharedPreferences sp = appContext.getSharedPreferences(getSharedPrefFileName(), 0);
165            HashMap<String, String> hashMap = (HashMap<String, String>) sp.getAll();
166            // Log.i(TAG, "restoreFromSharedPref: hashMap.size=" +
167            // hashMap.size());
168            for (HashMap.Entry<String, String> entry : hashMap.entrySet()) {
169                try {
170                    String key = entry.getKey();
171                    String value = entry.getValue();
172                    JSONObject json = new JSONObject(value);
173                    json.put(HLSDownloadableItem.SP_JSON_VALUE_URLMD5, key); // add
174                                                                             // md5_Url
175                    // Log.i(TAG, "restoreFromSharedPref: json=" +
176                    // json.toString());
177                    HLSDownloadableItem downloader = HLSDownloadableItem.restore(appContext,
178                            this.getSharedPrefFileName(), json);
179                    downloadMap.put(json.getString(HLSDownloadableItem.SP_JSON_VALUE_URL), downloader);
180                } catch (Exception e) {
181                    Log.d(TAG, "" + e.getMessage());
182                }
183            }
184        } catch (Exception e) {
185            Log.d(TAG, "" + e.getMessage());
186        }
187
188    }
189
190    /**
191     * 获得单个下载项目信息
192     * @param url 启动下载时传入的url
193     * @return
194     */
195    public DownloadableVideoItem getDownloadableVideoItemByUrl(String url) {
196        return downloadMap.get(url);
197    }
198
199    /**
200     * 获得所有下载项目
201     * read only
202     */
203    public HashMap<String, DownloadableVideoItem> getAllDownloadableVideoItems() {
204        HashMap<String, DownloadableVideoItem> result = new HashMap<String, DownloadableVideoItem>();
205        result.putAll(downloadMap);
206        return result;
207    }
208
209    /**
210     * 开始下载或者恢复下载
211     * @param url 待下载url
212     * @param token 校验token
213     * @param observer 观察者
214     */
215    public void startOrResumeDownloaderWithToken(String url, String token, DownloadObserver observer) {
216        if (url == null) {
217            Log.e(TAG, "url is null");
218            return;
219        }
220        HLSDownloadableItem hlsDownloader = downloadMap.get(url);
221        if (hlsDownloader == null) {
222            // create a downloader
223            String md5Url = DownloadUtils.getMD5(url);
224            String pathForCurrentUser = getDownloadRootForCurrentUser();
225            String folder = null;
226            if (pathForCurrentUser != null) {
227                folder = pathForCurrentUser + md5Url + "/";
228            }
229            
230            String fileName = md5Url + ".m3u8";
231            hlsDownloader = new HLSDownloadableItem(appContext, url, md5Url, folder, fileName,
232                    this.getSharedPrefFileName());
233            this.downloadMap.put(url, hlsDownloader);
234        }
235        // outside of creating new hlsDownloader, we allow to add observers more than one
236        if (observer != null) {
237            hlsDownloader.addObserver(observer);
238        }
239        if (token != null && !token.equals("")) {
240            hlsDownloader.setDrmToken(token);
241        }
242        if (tryStartOrPutInQueue(url)) {
243            // start to download directly
244            hlsDownloader.start();
245        } else {
246            // has been put into a suspending queue
247            hlsDownloader.setState(DownloadStatus.PENDING);
248            Log.w(TAG, "startOrResumeDownloader: too many tasks are downloading ,"
249                    + " this task is suspending now(will download automatically after other task down)");
250        }
251    }
252    /**
253     * 开始下载或者恢复下载
254     * 
255     * @param url
256     *            待下载url
257     * @param observer
258     *            观察者
259     * @return
260     */
261    public void startOrResumeDownloader(String url, DownloadObserver observer) {
262        startOrResumeDownloaderWithToken(url, null, observer);
263    }
264
265    /**
266     * 停止下载
267     * 
268     * @param url
269     * @return
270     */
271    public void pauseDownloader(String url) {
272        HLSDownloadableItem hlsDownloader = downloadMap.get(url);
273        if (hlsDownloader == null) {
274            Log.e(TAG, "pauseDownloader but there is no downloader for url=" + url);
275            return;
276        }
277        if (hlsDownloader.getStatus() == DownloadStatus.PENDING) {
278            removeFromQueue(url);
279        }
280
281        hlsDownloader.pause();
282    }
283    
284    /**
285     * check downloading tasks
286     * @param url
287     * @return true for StartNow; false for do nothing
288     */
289    protected boolean tryStartOrPutInQueue(String url) {
290        boolean shouldStart = true;
291        synchronized(queueList) {
292            if (!queueList.contains(url)) {
293                // check if start now
294                if (nowDownloadingSum >= maxDownloadingSum) {
295                    queueList.offer(url);
296                    shouldStart = false;
297                } else {
298                    nowDownloadingSum++;
299                    shouldStart = true;
300                }
301            } else {
302                shouldStart = false;
303            }
304        }
305        return shouldStart;
306    }
307    
308    protected void removeFromQueue(String url) {
309        synchronized(queueList) {
310            queueList.remove(url); // remove from queue
311        }
312    }
313    
314    protected void tryConsumeSuspendingTask() {
315        synchronized(queueList) {
316            if (queueList.isEmpty()) {
317                // no suspending task
318                if(nowDownloadingSum > 0) {
319                    nowDownloadingSum--;
320                }
321            } else {
322                // start one task from queue
323                String suspUrl = queueList.poll();
324                HLSDownloadableItem item = downloadMap.get(suspUrl);
325                if(item.getStatus() == DownloadStatus.PENDING) {
326                    item.start();
327                } else {
328                    if(nowDownloadingSum > 0) {
329                        nowDownloadingSum--;
330                    }
331                }
332            }
333        }
334    }
335
336    /**
337     * 删除下载
338     * 
339     * @param url
340     * @return
341     */
342    public void deleteDownloader(String url) {
343        HLSDownloadableItem hlsDownloader = downloadMap.get(url);
344        if (hlsDownloader == null) {
345            Log.e(TAG, "deleteDownloader but there is no downloader for url=" + url);
346            return;
347        }
348        if (hlsDownloader.getStatus() == DownloadStatus.PENDING) {
349            removeFromQueue(url);
350        }
351        hlsDownloader.removeSelf();
352        downloadMap.remove(url);
353    }
354
355    public String getUserName() {
356        return userName;
357    }
358
359    protected String getUserMd5Name() {
360        return md5User;
361    }
362
363    protected String getSharedPrefFileName() {
364        return SP_NAME_PREFIX + this.getUserMd5Name();
365    }
366
367    /**
368     * 更改最大并行下载数目,默认为5
369     * @param maxItems 需设置0-10之间的值
370     */
371    public void changeMaxDownloadingItems(int maxItems) {
372        if (maxItems > 0 && maxItems <= 10) {
373            maxDownloadingSum = maxItems;
374        } else {
375            Log.e(TAG, "changeMaxDownloadingItems, maxItems should be 0<x<100");
376        }
377        
378    }
379    
380    public String getDownloadRootForCurrentUser() {
381        File externalDirFile = appContext.getExternalFilesDir(null);
382        if (externalDirFile != null) {
383            return externalDirFile.getAbsolutePath() + "/" + DOWNLOAD_FOLDER + "/"
384                    + this.getUserMd5Name() + "/";
385        } else {
386            return null;
387        }
388    }
389}