1. 程式人生 > >Android手擼一個今日頭條視訊下載器

Android手擼一個今日頭條視訊下載器

前言

今日頭條是我最喜歡的app之一,當然喜歡並不是因為內容精彩,而是逗比的評論,而且看視訊的沒有廣告,我這個人喜歡收藏,尤其是小視訊(手動滑稽),可是卻沒有下載的按鈕,之後在仿今日頭條專案裡也需要用到視訊,進入網頁右鍵另存為也比較麻煩,作為程式猿,這可不是我們的辦事風格。於是動手擼了一個視訊下載器,喜歡的記得給個Star,當作是給我的鼓勵和動力吧。

成果圖

原始碼下載

分析視訊地址

這裡我摘出來視訊的獲取流程

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,整個下載的邏輯就寫完了

參考

宣告

這個屬於個人開發作品,僅做學習交流使用,切勿使用於商業用途,如用本程式做非法用途後果自負,與作者無關!!

相關推薦

Android一個今日頭條視訊下載

前言 今日頭條是我最喜歡的app之一,當然喜歡並不是因為內容精彩,而是逗比的評論,而且看視訊的沒有廣告,我這個人喜歡收藏,尤其是小視訊(手動滑稽),可是卻沒有下載的按鈕,之後在仿今日頭條專案裡也需要用到視訊,進入網頁右鍵另存為也比較麻煩,作為程式猿,這

今日頭條視訊下載[android下載原始碼]

在家無聊,看到趙四大神 寫的一個python指令碼下載今日頭條的工具,最後他還給出了移動端的樣子,可惜沒有原始碼,在他的虛心教導下,看完了他的文章,我決定自己擼一個,見笑了: CSDN圖片最大值只能傳2m,所以用了兩張gif圖,大家見笑了。 下面說一

Python指令碼下載今日頭條視訊 附加Android版本輔助下載

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Python指令碼下載今日頭條視訊(附加Android版本輔助下載)

一、前言今日頭條有毒,這句話不是虛的,現在資訊類app中也就大黃易和今日頭條可以博取使用者一點喜好了,我所說的喜好不是指內容精彩,而是評論,玩過這兩個app的人都知道,看的不是新聞本生內容,而是他逗逼的

如何下載今日頭條視訊

隨著《今日頭條》使用者數量越來越多,很多人都願意到《今日頭條》上去看視訊,但是有很多人並不知道《今日頭條》的視訊如何下載到電腦的,現在我介紹PC端的《今日頭條》視訊如何簡單的儲存到電腦上。 1.開啟瀏覽器,並進入今日頭條 2.找到你喜歡並想下載的視訊 3.

一個Android柱形圖和線型圖的組合圖表

專案開發中經常用到統計圖表,網上也有很多的圖表類庫,比如 :MPAndroidChart,XCL-chart,hellochart,AChartEngine等等,以前我最常用的就是MPAndroidChart,這個庫做的非常細緻用起來也簡單。 但是用別人的東

一個Vue滾動加載自定義指令

請求 tel document javascrip 決定 tlist win 滾動加載 pos 用Vue在移動端做滾動加載,使用mint-ui框架, InfiniteScroll指令loadmore組件,在uc瀏覽器和qq瀏覽器都無法觸發。無奈我只能自己寫了。 決定用vu

LRU原理和Redis實現——一個今日頭條的面試題

滿了 存儲空間 當前 node 硬盤 java 原理 remove http 看了評論,發現有些地方有問題,更新了圖和一些描述,希望可以更清晰一些,也歡迎關註,還會有幹貨文章 -------- 很久前參加過今日頭條的面試,遇到一個題,目前半部分是如何實現 LRU,後半部

分享一個抖音視訊下載程式

在網上呼叫別人的介面來實現的功能  import requests import execjs # 生成引數s def generateStr(a): js = ''' test = function(a) { var

使用Java Socket一個http服務

con body buffered run value nal uic news ble 原文連接:使用Java Socket手擼一個http服務器 作為一個java後端,提供http服務可以說是基本技能之一了,但是你真的了解http協議麽?你知道知道如何手擼一個htt

使用Java Socket一個http伺服器

原文連線:使用Java Socket手擼一個http伺服器 作為一個java後端,提供http服務可以說是基本技能之一了,但是你真的瞭解http協議麼?你知道知道如何手擼一個http伺服器麼?tomcat的底層是怎麼支援http服務的呢?大名鼎鼎的Servlet又是什麼東西呢,該怎麼使用呢? 在

仿今日頭條視訊播放

轉自:https://www.jianshu.com/p/34d378bffb00 gitbug:https://github.com/lipangit/JiaoZiVideoPlayer 參考:http://blog.csdn.net/androidstarjack/ar

Android使用SVG實現今日頭條下拉重新整理動畫

1 SVG的全稱是Scalable Vector Graphics,叫可縮放向量圖形。它和點陣圖(Bitmap)相對,SVG不會像點陣圖一樣因為縮放而讓圖片質量下降。 2 Android

一個預載入頁面,酷炫環形進度條

高仿格瓦拉生活預載入頁面環形進度條——我稱之為二龍戲珠。話不多說先上圖。 實現思路: 1、自定義一個view,畫兩個從點變換到半圓的弧形。 需要拓展的功能點: 1、需要展現出一個動畫效果。 2、進度條走滿的時候需要觸發介面的跳轉。 3、繪製的控制

android高仿系列)今日頭條 --新聞閱讀 (三) 完結 、總結 篇

    從寫第一篇今日頭條高仿系列開始,到現在已經過去了1個多月了,其實大體都做好了,就是遲遲沒有放出來,因為我覺得,做這個東西也是有個過程的,我想把這個模仿中一步一步學習的過程,按照自己的思路寫下來,在根據碰到的知識點和問題,並且羅列出這些東西的知識點和使用方法。如果你單

Android之高仿今日頭條、網易新聞首頁動態改變tab

前言: 專案需要一個類似今日頭條或者網易新聞首頁動態改變tab(頻道欄目)的功能,進過一番折騰,目前已實現該功能。 先看看效果圖: 思路: 1,關於tab欄目橫著滑動功能控制元件的選擇,這裡我採用的Horizontal

一個自定義日曆控制元件

引言 日曆控制元件在android開發中也是比較常見的一個控制元件,並且目前大部分開源的日曆控制元件也已經做得很漂亮,很完善了,功能也相當豐富; 今天這個日曆控制元件就是我在別人的基礎上進行修改了的,首先很感謝這個開源庫(https://github.com/c

一個ORM】第四步、Expression(表達式目錄樹)擴展

clas access stat ber expr req exc nodetype 支持 一、表達式目錄樹解析時需要的擴展方法 表達式操作符轉SQL操作符 獲取MemberExpression的根類型,後面需要根據該類型進行不同的操作 獲取表達式目錄路的值

一個ORM】第六步、對象表達式解析和Select表達式解析

member ++ access over def 大於 () 表達式 cti 對象表達式用於解析 Expression<Func<Student, object>> expr = s => new { s.Id, s.Name } 這種形式的

一個ORM】第八步、實體查詢和按需查詢

index desc cond ldb 主表 open 導航 sql handler 一、實體查詢 using MyOrm.Commons; using MyOrm.DbParameters; using MyOrm.Expressions; using MyO