实现效果:
-
在抖音复制分享链接
-
粘贴分享链接到App, 点击解析
-
点击
去下载
进入浏览器下载
获取无水印视频链接
首先我们使用浏览器打开分享的链接
8.97 teO:/ 一起玩水吧 在这炽热的夏天%%蓝色治愈系 %%夏天 %%游泳 https://v.douyin.com/Y5SpHKD/ 复制此链接,打开Dou音搜索,直接观看视频!
然后打开控制台找到对应的视频, 右键检查可以找到视频的链接
https://www.douyin.com/aweme/v1/play/?video_id=v0200fg10000ca08sgbc77udtm7m06p0
然后在浏览器中打开此链接发现是没有水印的视频, 稍后下载的视频使用的是此链接.
通过测试其他分享的视频发现不同视频对应的video_id不同, 于是需要找到获取video_id的请求.
https://www.douyin.com/aweme/v1/play/?video_id=xxx
通过控制台将设备改成移动端设备, 点击分享的链接
https://v.douyin.com/Y5SpHKD/
, 这时有一条请求进入我们的视线:
https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids=7097830584419962125
该请求的响应有我们需要的video_id
该请求有一个参数
item_ids
, 此参数正是点击分享链接后重定向链接的一部分
因此我们只需要解析该链接就可以拿到item_ids
至此,
就得到了下载无水印视频的全过程。
-
根据
https://v.douyin.com/Y5SpHKD/
的重定向地址
https://www.iesdouyin.com/share/video/7097830584419962125
获取item_ids=7097830584419962125 -
根据
https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids=7097830584419962125
的响应拿到vid -
根据
www.douyin.com/aweme/v1/play/?video_id=v0200fg10000ca08sgbc77udtm7m06p0
下载视频
实现
model层
通过传入的分享链接, 获取无水印视频链接
public class DouyinDownloadModel {
private final OkHttpClient mOkHttpClient = new OkHttpClient();
public void download(InputBean inputBean, DownloadListener downloadListener) {
Request.Builder builder = new Request.Builder().get().url(inputBean.getShareUrl());
requestInit(builder);
Request request = builder.build();
mOkHttpClient.newBuilder()
.followRedirects(false).build().newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
downloadListener.fail();
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) {
if (response.code() == 302) {
String location = response.header("location");
if (location != null) {
System.out.println(location);
String itemIds = parseItemIds(location);
if (!TextUtils.isEmpty(itemIds)) {
getVidByItemIds(itemIds, new GetVidRequestListener() {
@Override
public void success(JsonRootBean bean) {
Item_list item_list = bean.getItem_list().get(0);
String vid = item_list.getVideo().getVid();
String url = "https://www.douyin.com/aweme/v1/play/?video_id="
+ vid;
DownLoadResult downLoadResult = new DownLoadResult();
downLoadResult.setVideoUrl(url)
.setNickName(item_list.getAuthor().getNickname())
.setVideoDesc(item_list.getDesc());
downloadListener.success(downLoadResult);
}
@Override
public void fail() {
downloadListener.fail();
}
});
} else {
downloadListener.fail();
}
}
}
}
});
}
public void getVidByItemIds(String itemIds, GetVidRequestListener getVidRequestListener) {
String url = "https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids=" + itemIds;
System.out.println(url);
Request.Builder builder = new Request.Builder().get().url(url);
requestInit(builder);
Request request = builder.build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
getVidRequestListener.fail();
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response)
throws IOException {
if (response.code() == 200) {
response.body();
String jsonString = response.body().string();
Gson gson = new Gson();
System.out.println(jsonString);
JsonRootBean jsonRootBean = gson.fromJson(jsonString, JsonRootBean.class);
System.out.println(jsonRootBean.getItem_list().get(0).getVideo().getVid());
getVidRequestListener.success(jsonRootBean);
}
}
});
}
public interface DownloadListener {
void success(DownLoadResult downLoadResult);
void fail();
void onProgress(int progress);
}
interface GetVidRequestListener {
void success(JsonRootBean bean);
void fail();
}
public void requestInit(Request.Builder builder) {
builder.addHeader("user-agent",
"Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 "
+ "(KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1 Edg/101.0.4951.41");
}
public String parseItemIds(String text) {
String regex = "\\d{19}";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(text);
if (m.find()) {
return m.group();
}
return null;
}
}
Presenter层
略
View层
略
参考