当前位置: 首页 > 编程笔记 >

Android Studio 下载视频到本地

东龙野
2023-03-14
本文向大家介绍Android Studio 下载视频到本地,包括了Android Studio 下载视频到本地的使用技巧和注意事项,需要的朋友参考一下

最近在研究视频下载到本地的问题,像爱奇艺,腾讯视频,迅雷看看等等一些视频播放器,如果在一个播放器里面视频下载到一半用户退出App之后,再次登录从头开始,那么就太可悲了,所以在做视频音频类的项目时,要实现的一个功能就是断点续传,就是将用户下载的视频或者音频等以字节流的形式存入数据库,下次用户再次下载时,将继续上次数据库的接着下载,这样用户体验就会很好,也大大节省了成本.

好了废话不多说,开始今天的正题.

一、先上效果图

二、使用GreenDao我们需要导入依赖

1.以下在项目gradle依赖中添加

compile 'org.greenrobot:greendao:3.2.2'//依赖 在最后一行插入
apply plugin: 'org.greenrobot.greendao' //greenDao在第二行插入
greendao {//在依赖导入汇总添加一个自动添加数据库表名的配置依赖
 schemaVersion 1 //数据库版本号
 daoPackage 'com.example.greendaodemo.database' //设置时生成代码的目录
 targetGenDir 'src/main/java' //设置DaoMaster、DaoSession、Dao目录
}

2.以下在工程gradle依赖中添加

classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'

3.既然我们要联网下载,读写权限肯定不能忘记了

<uses-permission android:name="android.permission.INTERNET"/> 
 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> 
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> 
 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> 
 <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" /> 

三、当然还要有实体类写入实体类后,build下make project一下,让他自动生成GreenDao的三个类

import org.greenrobot.greendao.annotation.Entity; 
import org.greenrobot.greendao.annotation.Id; 
import org.greenrobot.greendao.annotation.Generated; 
/** 
 * author:Created by ZhangPengFei. 
 * Email: 1271396448@qq.com 
 * data: 2018/1/18 
 */ 
@Entity 
public class User { 
 @Id 
 private Long id; 
 private Integer thread_id; 
 private Integer start_pos; 
 private Integer end_pos; 
 private Integer compelete_size; 
 private String url; 
 @Generated(hash = 2041931179) 
 public User(Long id, Integer thread_id, Integer start_pos, Integer end_pos, 
   Integer compelete_size, String url) { 
  this.id = id; 
  this.thread_id = thread_id; 
  this.start_pos = start_pos; 
  this.end_pos = end_pos; 
  this.compelete_size = compelete_size; 
  this.url = url; 
 } 
 @Generated(hash = 586692638) 
 public User() { 
 } 
 public Long getId() { 
  return this.id; 
 } 
 public void setId(Long id) { 
  this.id = id; 
 } 
 public Integer getThread_id() { 
  return this.thread_id; 
 } 
 public void setThread_id(Integer thread_id) { 
  this.thread_id = thread_id; 
 } 
 public Integer getStart_pos() { 
  return this.start_pos; 
 } 
 public void setStart_pos(Integer start_pos) { 
  this.start_pos = start_pos; 
 } 
 public Integer getEnd_pos() { 
  return this.end_pos; 
 } 
 public void setEnd_pos(Integer end_pos) { 
  this.end_pos = end_pos; 
 } 
 public Integer getCompelete_size() { 
  return this.compelete_size; 
 } 
 public void setCompelete_size(Integer compelete_size) { 
  this.compelete_size = compelete_size; 
 } 
 public String getUrl() { 
  return this.url; 
 } 
 public void setUrl(String url) { 
  this.url = url; 
 } 
} 

四、本人使用了单例模式,所以在全局配置里初始化了数据库的操作

import android.app.Application; 
import com.example.greendaodemo.database.DaoMaster; 
import com.example.greendaodemo.database.DaoSession; 
import com.example.greendaodemo.database.UserDao; 
/** 
 * author:Created by ZhangPengFei. 
 * Email: 1271396448@qq.com 
 * data: 2018/1/18 
 */ 
public class App extends Application { 
 public static UserDao userDao; 
 @Override 
 public void onCreate() { 
  super.onCreate(); 
  DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(this, "zpf.db", null); 
  DaoMaster daoMaster = new DaoMaster(devOpenHelper.getWritableDb()); 
  DaoSession daoSession = daoMaster.newSession(); 
  userDao = daoSession.getUserDao(); 
 } 
} 

五、因为我们是多线程,所以写一个保存每个线程下载信息的类

public class DownLoadInfo { 
 /** 
  * 保存每个下载线程下载信息类 
  * 
  */ 
 private int threadId;// 下载器id 
 private int startPos;// 开始点 
 private int endPos;// 结束点 
 private int compeleteSize;// 完成度 
 private String url;// 下载文件的URL地址 
 public DownLoadInfo(int threadId, int startPos, int endPos, 
      int compeleteSize, String url) { 
  this.threadId = threadId; 
  this.startPos = startPos; 
  this.endPos = endPos; 
  this.compeleteSize = compeleteSize; 
  this.url = url; 
 } 
 public DownLoadInfo() { 
 } 
 public String getUrl() { 
  return url; 
 } 
 public void setUrl(String url) { 
  this.url = url; 
 } 
 public int getThreadId() { 
  return threadId; 
 } 
 public void setThreadId(int threadId) { 
  this.threadId = threadId; 
 } 
 public int getStartPos() { 
  return startPos; 
 } 
 public void setStartPos(int startPos) { 
  this.startPos = startPos; 
 } 
 public int getEndPos() { 
  return endPos; 
 } 
 public void setEndPos(int endPos) { 
  this.endPos = endPos; 
 } 
 public int getCompeleteSize() { 
  return compeleteSize; 
 } 
 public void setCompeleteSize(int compeleteSize) { 
  this.compeleteSize = compeleteSize; 
 } 
 @Override 
 public String toString() { 
  return "DownloadInfo [threadId=" + threadId + ", startPos=" + startPos 
    + ", endPos=" + endPos + ", compeleteSize=" + compeleteSize 
    + "]"; 
 } 
} 

六、写一个用来记录的类,修改,添加和下载完清空数据库的操作

import android.util.Log; 
import com.example.greendaodemo.database.UserDao; 
import java.util.ArrayList; 
import java.util.List; 
import static com.bwie.mtd.App.userDao; 
/** 
 * author:Created by ZhangPengFei. 
 * Email: 1271396448@qq.com 
 * data: 2018/1/18 
 */ 
public class DownLoadSqlTool { 
 /** 
  * 创建下载的具体信息 
  */ 
 public void insertInfos(List<DownLoadInfo> infos) { 
  for (DownLoadInfo info : infos) { 
   User user = new User(null, info.getThreadId(), info.getStartPos(), info.getEndPos(), info.getCompeleteSize(), info.getUrl()); 
   userDao.insert(user); 
  } 
 } 
 /** 
  * 得到下载具体信息 
  */ 
 public List<DownLoadInfo> getInfos(String urlstr) { 
  List<DownLoadInfo> list = new ArrayList<DownLoadInfo>(); 
  List<User> list1 = userDao.queryBuilder().where(UserDao.Properties.Url.eq(urlstr)).build().list(); 
  for (User user : list1) { 
   DownLoadInfo infoss = new DownLoadInfo( 
     user.getThread_id(), user.getStart_pos(), user.getEnd_pos(), 
     user.getCompelete_size(), user.getUrl()); 
   Log.d("main-----", infoss.toString()); 
   list.add(infoss); 
  } 
  return list; 
 } 
 /** 
  * 更新数据库中的下载信息 
  */ 
 public void updataInfos(int threadId, int compeleteSize, String urlstr) { 
  User user = userDao.queryBuilder() 
    .where(UserDao.Properties.Thread_id.eq(threadId), UserDao.Properties.Url.eq(urlstr)).build().unique(); 
  user.setCompelete_size(compeleteSize); 
  userDao.update(user); 
 } 
 /** 
  * 下载完成后删除数据库中的数据 
  */ 
 public void delete(String url) { 
  userDao.deleteAll(); 
 } 
} 

七、多线程下载的实践类

import android.content.Context; 
import android.os.Handler; 
import android.os.Message; 
import android.util.Log; 
import java.io.File; 
import java.io.InputStream; 
import java.io.RandomAccessFile; 
import java.net.HttpURLConnection; 
import java.net.URL; 
import java.util.ArrayList; 
import java.util.List; 
/** 
 * author:Created by ZhangPengFei. 
 * Email: 1271396448@qq.com 
 * data: 2018/1/18 
 */ 
public class DownloadHttpTool { 
 /** 
  * 利用Http协议进行多线程下载具体实践类 
  */ 
 private static final String TAG = DownloadHttpTool.class.getSimpleName(); 
 private int threadCount;//线程数量 
 private String urlstr;//URL地址 
 private Context mContext; 
 private Handler mHandler; 
 private List<DownLoadInfo> downloadInfos;//保存下载信息的类 
 private String localPath;//目录 
 private String fileName;//文件名 
 private int fileSize; 
 private DownLoadSqlTool sqlTool;//文件信息保存的数据库操作类 
 private enum Download_State { 
  Downloading, Pause, Ready;//利用枚举表示下载的三种状态 
 } 
 private Download_State state = Download_State.Ready;//当前下载状态 
 private int globalCompelete = 0;//所有线程下载的总数 
 public DownloadHttpTool(int threadCount, String urlString, 
       String localPath, String fileName, Context context, Handler handler) { 
  super(); 
  this.threadCount = threadCount; 
  this.urlstr = urlString; 
  this.localPath = localPath; 
  this.mContext = context; 
  this.mHandler = handler; 
  this.fileName = fileName; 
  sqlTool = new DownLoadSqlTool(); 
 } 
 //在开始下载之前需要调用ready方法进行配置 
 public void ready() { 
  Log.w(TAG, "ready"); 
  globalCompelete = 0; 
  downloadInfos = sqlTool.getInfos(urlstr); 
  if (downloadInfos.size() == 0) { 
   initFirst(); 
  } else { 
   File file = new File(localPath + "/" + fileName); 
   if (!file.exists()) { 
    sqlTool.delete(urlstr); 
    initFirst(); 
   } else { 
    fileSize = downloadInfos.get(downloadInfos.size() - 1) 
      .getEndPos(); 
    for (DownLoadInfo info : downloadInfos) { 
     globalCompelete += info.getCompeleteSize(); 
    } 
    Log.w(TAG, "globalCompelete:::" + globalCompelete); 
   } 
  } 
 } 
 public void start() { 
  Log.w(TAG, "start"); 
  if (downloadInfos != null) { 
   if (state == Download_State.Downloading) { 
    return; 
   } 
   state = Download_State.Downloading; 
   for (DownLoadInfo info : downloadInfos) { 
    Log.v(TAG, "startThread"); 
    new DownloadThread(info.getThreadId(), info.getStartPos(), 
      info.getEndPos(), info.getCompeleteSize(), 
      info.getUrl()).start(); 
   } 
  } 
 } 
 public void pause() { 
  state = Download_State.Pause; 
 } 
 public void delete() { 
  compelete(); 
  File file = new File(localPath + "/" + fileName); 
  file.delete(); 
 } 
 public void compelete() { 
  sqlTool.delete(urlstr); 
 } 
 public int getFileSize() { 
  return fileSize; 
 } 
 public int getCompeleteSize() { 
  return globalCompelete; 
 } 
 //第一次下载初始化 
 private void initFirst() { 
  Log.w(TAG, "initFirst"); 
  try { 
   URL url = new URL(urlstr); 
   HttpURLConnection connection = (HttpURLConnection) url 
     .openConnection(); 
   connection.setConnectTimeout(5000); 
   connection.setRequestMethod("GET"); 
   fileSize = connection.getContentLength(); 
   Log.w(TAG, "fileSize::" + fileSize); 
   File fileParent = new File(localPath); 
   if (!fileParent.exists()) { 
    fileParent.mkdir(); 
   } 
   File file = new File(fileParent, fileName); 
   if (!file.exists()) { 
    file.createNewFile(); 
   } 
   // 本地访问文件 
   RandomAccessFile accessFile = new RandomAccessFile(file, "rwd"); 
   accessFile.setLength(fileSize); 
   accessFile.close(); 
   connection.disconnect(); 
  } catch (Exception e) { 
   e.printStackTrace(); 
  } 
  int range = fileSize / threadCount; 
  downloadInfos = new ArrayList<DownLoadInfo>(); 
  for (int i = 0; i < threadCount - 1; i++) { 
   DownLoadInfo info = new DownLoadInfo(i, i * range, (i + 1) * range 
     - 1, 0, urlstr); 
   downloadInfos.add(info); 
  } 
  DownLoadInfo info = new DownLoadInfo(threadCount - 1, (threadCount - 1) 
    * range, fileSize - 1, 0, urlstr); 
  downloadInfos.add(info); 
  sqlTool.insertInfos(downloadInfos); 
 } 
 //自定义下载线程 
 private class DownloadThread extends Thread { 
  private int threadId; 
  private int startPos; 
  private int endPos; 
  private int compeleteSize; 
  private String urlstr; 
  private int totalThreadSize; 
  public DownloadThread(int threadId, int startPos, int endPos, 
        int compeleteSize, String urlstr) { 
   this.threadId = threadId; 
   this.startPos = startPos; 
   this.endPos = endPos; 
   totalThreadSize = endPos - startPos + 1; 
   this.urlstr = urlstr; 
   this.compeleteSize = compeleteSize; 
  } 
  @Override 
  public void run() { 
   HttpURLConnection connection = null; 
   RandomAccessFile randomAccessFile = null; 
   InputStream is = null; 
   try { 
    randomAccessFile = new RandomAccessFile(localPath + "/" 
      + fileName, "rwd"); 
    randomAccessFile.seek(startPos + compeleteSize); 
    URL url = new URL(urlstr); 
    connection = (HttpURLConnection) url.openConnection(); 
    connection.setConnectTimeout(5000); 
    connection.setRequestMethod("GET"); 
    connection.setRequestProperty("Range", "bytes=" 
      + (startPos + compeleteSize) + "-" + endPos); 
    is = connection.getInputStream(); 
    byte[] buffer = new byte[1024]; 
    int length = -1; 
    while ((length = is.read(buffer)) != -1) { 
     randomAccessFile.write(buffer, 0, length); 
     compeleteSize += length; 
     Message message = Message.obtain(); 
     message.what = threadId; 
     message.obj = urlstr; 
     message.arg1 = length; 
     mHandler.sendMessage(message); 
     sqlTool.updataInfos(threadId, compeleteSize, urlstr); 
     Log.w(TAG, "Threadid::" + threadId + " compelete::" 
       + compeleteSize + " total::" + totalThreadSize); 
     if (compeleteSize >= totalThreadSize) { 
      break; 
     } 
     if (state != Download_State.Downloading) { 
      break; 
     } 
    } 
   } catch (Exception e) { 
    e.printStackTrace(); 
   } finally { 
    try { 
     if (is != null) { 
      is.close(); 
     } 
     randomAccessFile.close(); 
     connection.disconnect(); 
    } catch (Exception e) { 
     e.printStackTrace(); 
    } 
   } 
  } 
 } 
} 

八、再写一个接口类

import android.annotation.SuppressLint; 
import android.content.Context; 
import android.os.AsyncTask; 
import android.os.Handler; 
import android.os.Message; 
import android.util.Log; 
/** 
 * author:Created by ZhangPengFei. 
 * Email: 1271396448@qq.com 
 * data: 2018/1/18 
 */ 
public class DownloadUtil { 
 private DownloadHttpTool mDownloadHttpTool; 
 private OnDownloadListener onDownloadListener; 
 private int fileSize; 
 private int downloadedSize = 0; 
 @SuppressLint("HandlerLeak") 
 private Handler mHandler = new Handler() { 
  @Override 
  public void handleMessage(Message msg) { 
   // TODO Auto-generated method stub 
   super.handleMessage(msg); 
   int length = msg.arg1; 
   synchronized (this) {//加锁保证已下载的正确性 
    downloadedSize += length; 
   } 
   if (onDownloadListener != null) { 
    onDownloadListener.downloadProgress(downloadedSize); 
   } 
   if (downloadedSize >= fileSize) { 
    mDownloadHttpTool.compelete(); 
    if (onDownloadListener != null) { 
     onDownloadListener.downloadEnd(); 
    } 
   } 
  } 
 }; 
 public DownloadUtil(int threadCount, String filePath, String filename, 
      String urlString, Context context) { 
  mDownloadHttpTool = new DownloadHttpTool(threadCount, urlString, 
    filePath, filename, context, mHandler); 
 } 
 //下载之前首先异步线程调用ready方法获得文件大小信息,之后调用开始方法 
 public void start() { 
  new AsyncTask<Void,Void,Void>() { 
   @Override 
   protected Void doInBackground(Void... arg0) { 
    // TODO Auto-generated method stub 
    mDownloadHttpTool.ready(); 
    return null; 
   } 
   @Override 
   protected void onPostExecute(Void result) { 
    // TODO Auto-generated method stub 
    super.onPostExecute(result); 
    fileSize = mDownloadHttpTool.getFileSize(); 
    downloadedSize = mDownloadHttpTool.getCompeleteSize(); 
    Log.w("Tag", "downloadedSize::" + downloadedSize); 
    if (onDownloadListener != null) { 
     onDownloadListener.downloadStart(fileSize); 
    } 
    mDownloadHttpTool.start(); 
   } 
  }.execute(); 
 } 
 public void pause() { 
  mDownloadHttpTool.pause(); 
 } 
 public void delete(){ 
  mDownloadHttpTool.delete(); 
 } 
 public void reset(){ 
  mDownloadHttpTool.delete(); 
  start(); 
 } 
 public void setOnDownloadListener(OnDownloadListener onDownloadListener) { 
  this.onDownloadListener = onDownloadListener; 
 } 
 //下载回调接口 
 public interface OnDownloadListener { 
  public void downloadStart(int fileSize); 
  public void downloadProgress(int downloadedSize);//记录当前所有线程下总和 
  public void downloadEnd(); 
 } 
} 

九、写一下自己的布局文件

<?xml version="1.0" encoding="utf-8"?> 
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
 xmlns:app="http://schemas.android.com/apk/res-auto" 
 xmlns:tools="http://schemas.android.com/tools" 
 android:layout_width="match_parent" 
 android:layout_height="match_parent" 
 tools:context="com.mtd.MainActivity"> 
 <ProgressBar 
  android:id="@+id/progressBar" 
  style="?android:attr/progressBarStyleHorizontal" 
  android:layout_width="fill_parent" 
  android:layout_height="7.5dp" 
  android:layout_centerInParent="true" 
  android:layout_marginRight="8dp" 
  android:max="100" 
  android:progress="100" 
  android:visibility="visible" /> 
 <TextView 
  android:id="@+id/tv_Progress" 
  android:layout_width="wrap_content" 
  android:layout_height="wrap_content" 
  android:layout_below="@id/progressBar" 
  android:layout_centerHorizontal="true" 
  android:layout_marginLeft="23dp" 
  android:layout_marginStart="23dp" 
  android:layout_marginTop="18dp" 
  android:text="" /> 
 <Button 
  android:id="@+id/downLoad" 
  android:layout_width="wrap_content" 
  android:layout_height="wrap_content" 
  android:layout_alignParentBottom="true" 
  android:layout_marginLeft="100dp" 
  android:text="下载" /> 
 <Button 
  android:id="@+id/pause" 
  android:layout_toRightOf="@id/downLoad" 
  android:layout_width="wrap_content" 
  android:layout_height="wrap_content" 
  android:layout_alignParentBottom="true" 
  android:layout_marginLeft="48dp" 
  android:layout_marginStart="48dp" 
  android:text="暂停" /> 
</RelativeLayout> 

十、奉上自己的Java代码MainActivity类

import android.os.Bundle; 
import android.os.Environment; 
import android.support.v7.app.AppCompatActivity; 
import android.util.Log; 
import android.view.View; 
import android.widget.Button; 
import android.widget.ProgressBar; 
import android.widget.TextView; 
public class MainActivity extends AppCompatActivity { 
 private final String videoUrl = "http://2449.vod.myqcloud.com/2449_22ca37a6ea9011e5acaaf51d105342e3.f20.mp4"; 
 private TextView tv_progress;//进度显示 
 private ProgressBar progressBar;//进度条 
 private Button downLoad;//下载按钮 
 private Button pause;//暂停按钮 
 private String path;//下载路径 
 private int max; 
 @Override 
 protected void onCreate(Bundle savedInstanceState) { 
  super.onCreate(savedInstanceState); 
  setContentView(R.layout.activity_main); 
  tv_progress = (TextView) findViewById(R.id.tv_Progress); 
  progressBar = (ProgressBar) findViewById(R.id.progressBar); 
  downLoad = (Button) findViewById(R.id.downLoad); 
  pause = (Button) findViewById(R.id.pause); 
  path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/local"; 
  final DownloadUtil downloadUtil = new DownloadUtil(4, path, "drum.mp4", videoUrl, this); 
  downloadUtil.setOnDownloadListener(new DownloadUtil.OnDownloadListener() { 
   @Override 
   public void downloadStart(int fileSize) { 
    Log.i("TAG---fileSize", fileSize + ""); 
    max = fileSize;//文件总长度 
    progressBar.setMax(fileSize); 
   } 
   @Override 
   public void downloadProgress(int downloadedSize) { 
    Log.i("TAG---downloadedSize", downloadedSize + ""); 
    progressBar.setProgress(downloadedSize); 
    tv_progress.setText((int) downloadedSize * 100 / max + "%"); 
   } 
   @Override 
   public void downloadEnd() { 
    Log.i("TAG---end", "End"); 
   } 
  }); 
  /** 
   * 下载的点击事件 
   */ 
  downLoad.setOnClickListener(new View.OnClickListener() { 
   @Override 
   public void onClick(View view) { 
    downloadUtil.start(); 
   } 
  }); 
  /** 
   * 暂停的点击事件 
   */ 
  pause.setOnClickListener(new View.OnClickListener() { 
   @Override 
   public void onClick(View view) { 
    downloadUtil.pause(); 
   } 
  }); 
 } 
} 
 类似资料:
  • 在视频影像商店可购买或租赁视频影像。 可通过与「下载内容」相同的方法下载内容。已下载的内容可进入(视频影像)观赏。 若要使用(PS Store),需先注册PlayStation®Network账号。即使未使用PS Vita注册PlayStation®Network也可阅览PlayStation®Store。下载内容时需先注册,请遵循显示的画面注册。 PlayStation®Network和Play

  • 编辑:经过一些测试后,这里是关于这个主题的一些附加信息。我试着从互联网上获取音频的视频只是一个示例mp4文件: http://download.wavetlan.com/svv/media/http/mp4/convertedfiles/mediaCoder/mediaCoder_test6_1m9s_xvid_vbr_306kbps_320x240_25fps_mpeg1layer3_cbr_3

  • 问题内容: new YouTubePageStreamUriGetter().execute( 该代码不起作用 问题答案: 编辑3 您可以使用Lib:https : //github.com/HaarigerHarald/android- youtubeExtractor 例如: 他们使用以下方法解密Signature: 现在,使用此库, 高质量视频会 丢失 音频,因此我将MediaMuxer用于

  • 问题内容: 我知道有上百万种方法可以从youtube下载视频,然后将其转换为音频或对其进行进一步处理。但是最近,我很惊讶地在Mac上看到一个名为YoutubeToMp3的应用程序实际显示“正在跳过X mb视频”,并且据说只从视频中下载音频,而无需使用bandwith下载整个视频然后进行转换。我想知道这是否真的正确,并且根本不可能,因为我找不到任何方法可以做到这一点。你有什么想法 ? 编辑:经过一些

  • 问题内容: 感谢downloadTaskWithURL,我正在下载视频,并使用以下代码将其保存到我的画廊: 它在我的模拟器上可以正常工作,但是在我的iPad上,即使出现视频也不会保存视频。你知道为什么吗? 我的控制台中也出现了该消息 无法从文件创建数据(空) 问题答案: 请通过代码检查注释: Xcode 8•Swift 3

  • 想下载S3签名视频文件…但不是下载它将要播放。。。 使用send_dataS3下载 视频文件=”https://s3-eu-west-1.amazonaws.com/tokboxhub.mangoapps.com/46250362/2e1ad9d5-8240-41d2-82bc-38c34bf92e7e/archive.mp4?X-Amz算法=AWS4-HMAC-SHA256