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}