您的位置:首页 > 移动开发 > Android开发

Android手撸一个今日头条视频下载器

2017-02-16 16:00 246 查看
转载请标明出处: http://www.weyye.me/detail/today-news-video/

本文出自:【Wey Ye的博客】

前言

今日头条是我最喜欢的app之一,当然喜欢并不是因为内容精彩,而是逗比的评论,而且看视频的没有广告,我这个人喜欢收藏,尤其是小视频(手动滑稽),可是却没有下载的按钮,之后在仿今日头条项目里也需要用到视频,进入网页右键另存为也比较麻烦,作为程序猿,这可不是我们的办事风格。于是动手撸了一个视频下载器,喜欢的记得给个Star,当作是给我的鼓励和动力吧。

成果图





源码下载

https://github.com/yewei02538/TodayNewsVideoDownloader

分析视频地址

详细的视频地址分析请看Python脚本下载今日头条视频(附加Android版本辅助下载器)

这里我摘出来视频的获取流程

1、将/video/urls/v/1/toutiao/mp4/{videoid}?r={Math.random()},进行crc32加密。

2、将上面得到的加密值拼接到上面的链接中即可,最终的链接形式是:

http://i.snssdk.com/video/urls/v/1/toutiao/mp4/{videoid}?r={Math.random()}&s={crc32值}

3、访问这个链接得到一个json数据,需要解析video_list数组中的main_url值,然后用base64解码得到最终的原始视频链接。

看到上面的步骤并不复杂,但是在操作过程中还是有些地方需要注意的,主要是上面的那个随机数和crc32加密逻辑,videoid可以从视频网页的html源代码里面获取



用正则表达式取出videoid即可

看流程分析,我们需要视频所在的网页地址,在get请求访问成功的回调里面进行正则表达式匹配,然后将/video/urls/v/1/toutiao/mp4/{videoid}?r={Math.random()},进行crc32加密,然后拼在一起,再get请求,再在请求成功的回调里解析json获取base64编码的地址,然后进行解码,最后获得视频源地址。这一些列操作都是需要请求成功才可以执行的,可以想到嵌套里面再嵌套,那种代码逻辑着实让人看着蛋疼,所以这个时候RxJava的优势就出来了,完美的解决了这个问题,如果还不是很懂RxJava的朋友可以去看下这篇文章给 Android 开发者的 RxJava 详解

撸代码

首先得先获取到播放视频的网页地址,这里我使用的是分享来接收,今日头条有分享功能,分享的内容里面肯定会有地址,所以我们来配置下接收分享

<activity
android:name=".ui.MainActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>

<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND"/>

<category android:name="android.intent.category.DEFAULT"/>

<data android:mimeType="text/plain"/>
</intent-filter>
</activity>


ok,这样就具备的接收的功能,然后在
MainActivity
里面写上接受的逻辑

protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.i("MainActivity", "onNewIntent");
String title = intent.getStringExtra(Intent.EXTRA_TEXT);
parseUrl(title);
}
private void parseUrl(String title) {
//取出网页地址
Pattern pattern = Pattern.compile("【(.+)】\\n(http.+)");

final Matcher matcher = pattern.matcher(title);
if (matcher.find()) {

final ProgressDialog dialog = new ProgressDialog(this);
dialog.setMessage("正在获取视频地址,请稍后~");
dialog.setCanceledOnTouchOutside(false);
//解析视频真实地址
VideoPathDecoder decoder = new VideoPathDecoder() {
@Override
public void onSuccess(Video s) {
dialog.dismiss();
s.title = matcher.group(1);
mDatas.add(s);
mAdapter.notifyItemInserted(mDatas.size());
startDownload(s);
}

@Override
public void onDecodeError(Throwable e) {
dialog.dismiss();
Snackbar.make(mRecyclerView, "获取视频失败!", Snackbar.LENGTH_LONG).show();
}
};
dialog.show();
decoder.decodePath(matcher.group(2));
} else {
Snackbar.make(mRecyclerView, "不是分享的链接", Snackbar.LENGTH_LONG).show();
}
}


首先通过正则表达式取出分享链接,然后进行视频解析

视频解析的核心代码

AppClient.getApiService().getVideoHtml(srcUrl)
.flatMap(new Func1<String, Observable<ResultResponse<VideoModel>>>() {
@Override
public Observable<ResultResponse<VideoModel>> call(String response) {
Pattern pattern = Pattern.compile("videoid:\'(.+)\'");
Matcher matcher = pattern.matcher(response);
if (matcher.find()) {
String videoId = matcher.group(1);
Log.i(TAG,videoId);
//1.将/video/urls/v/1/toutiao/mp4/{videoid}?r={Math.random()},进行crc32加密。
String r = getRandom();
CRC32 crc32 = new CRC32();
String s = String.format(ApiService.URL_VIDEO, videoId, r);
//进行crc32加密。
crc32.update(s.getBytes());
String crcString = crc32.getValue() + "";
//2.访问http://i.snssdk.com/video/urls/v/1/toutiao/mp4/{videoid}?r={Math.random()}&s={crc32值}
String url = ApiService.HOST_VIDEO + s + "&s=" + crcString;
Log.i(TAG,url);
return AppClient.getApiService().getVideoData(url);
}
return null;
}
})
.map(new Func1<ResultResponse<VideoModel>, Video>() {
@Override
public Video call(ResultResponse<VideoModel> videoModelResultResponse) {
VideoModel.VideoListBean data = videoModelResultResponse.data.video_list;

if (data.video_3 != null) {
return updateVideo(data.video_3);
}
if (data.video_2 != null) {
return updateVideo(data.video_2);
}
if (data.video_1 != null) {
return updateVideo(data.video_1);
}
return null;
}

private String getRealPath(String base64) {
return new String(Base64.decode(base64.getBytes(), Base64.DEFAULT));
}

private Video updateVideo(Video video) {
//base64解码
video.main_url = getRealPath(video.main_url);
return video;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Video>() {
@Override
public void onCompleted() {

}

@Override
public void onError(Throwable e) {
e.printStackTrace();
onDecodeError(e);
}

@Override
public void onNext(Video s) {
onSuccess(s);
}
});


ok,一套流程下来,我们就获取到视频的真实地址

视频获取出来,就可以下载了,

private void startDownload(final Video video) {

FileDownload download = new FileDownload(Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "todayNewsVideo"
, UUID.randomUUID().toString() + "." + video.vtype);
download.download(video.main_url, new FileDownload.Callback() {
@Override
public void onError(Exception e) {
mAdapter.setPercent(video.main_url, -1);
}

@Override
public void onSuccess(File file) {
video.file = file;
mAdapter.setPercent(video.main_url, 100);
}

@Override
public void inProgress(float progress, long total) {
mAdapter.setPercent(video.main_url, (int) (progress * 100));
}
});

}


这里我没有使用
retrofit
,因为下载大文件的时候容易oom(希望有大神能解决我这个问题)为了方便,我直接使用原生的okhttp来下载

public void download(String url, final Callback callback) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
client.newCall(request).enqueue(new okhttp3.Callback() {
@Override
public void onFailure(Call call, IOException e) {
callback.onError(e);
}

@Override
public void onResponse(Call call, Response response) throws IOException {
try {
final File file = saveFile(response, callback);
AppClient.getDelivery().post(new Runnable() {
@Override
public void run() {
callback.onSuccess(file);
}
});
} catch (IOException e) {
callback.onError(e);
}
}
});
}


ok,整个下载的逻辑就写完了

参考

Python脚本下载今日头条视频(附加Android版本辅助下载器)

Android Canvas Clear with transparency

声明

这个属于个人开发作品,仅做学习交流使用,切勿使用于商业用途,如用本程序做非法用途后果自负,与作者无关!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息