Android 资讯类App项目实战 第五章 视频模块

前言:

正在做一个资讯类app,打算一边做一边整理,供自己学习与巩固。用到的知识复杂度不高,仅适于新手。经验不多,如果写出来的代码有不好的地方欢迎讨论。

以往的内容

第一章 滑动顶部导航栏

第二章 retrofit获取网络数据

第三章 新闻模块

第四章 电影模块

第五章 视频模块

本章内容最终效果:

视频模块效果.gif

知识点:

MVP,RxJava,RecyclerView,JZVideoPlayerStandard

学习目标:

1、MVP模式的使用

2、使用RxJava处理复杂请求过程。

3、使用RecyclerView显示视频列表数据。

视频模块的视频播放方面主要用了第三方视频库JiaoZiVideoPlayer。除了网络视频播放,本章还运用RxJava解决复杂的网络请求(嵌套请求和循环请求)。

项目实战:

注意

本章用到的drawable资源、values资源皆存放在百度网盘

(请将values文件夹中的style.xml或color.xml更新一致后再运行,如有后续更新自行修改)

1.1 项目结构

image.png

需导入的库:
导入JiaoZiVideoPlayer。

compile 'cn.jzvd:jiaozivideoplayer:6.2.10'
image.png

用到的Api:
http://is.snssdk.com/api/news/feed/v51/?category=video
http://ib.365yg.com/video/urls/v/1/toutiao/mp4/v02004f00000bbpbk3l2v325q7lmkds0?r=6781281688452415&s=2734808831

1.2 属性类

本章分别用到3个属性类:TodayBean,TodayContentBean和VideoUrlBean。TodayBean是头条视频信息的数据,但因为返回的数据里最重要的content数据是个字符串,需要用我们将它解析成Json对象,所以诞生了TodayContentBean。VideoUrlBean则是我们获取视频地址需要用到的数据。

TodayBean的内容大家通过api:http://is.snssdk.com/api/news/feed/v51/?category=video返回数据,再复制用GsonFormat生成就好。
VideoUrlBean同理,用api:
http://ib.365yg.com/video/urls/v/1/toutiao/mp4/v02004f00000bbpbk3l2v325q7lmkds0?r=6781281688452415&s=2734808831

这里TodayContentBean的生成比较麻烦,我直接提供给大家content部分的json解析,大家复制后用GsonFormat生成:

{
"abstract":"视频讲述: 现在相亲都不问房子和车了, 都开始问这个问题, 结局亮了。","action_extra":"{\"channel_id\": 3431225546}","action_list":[{"action":1,"desc":"","extra":{}},{"action":3,"desc":"","extra":{}},{"action":7,"desc":"","extra":{}},{"action":9,"desc":"","extra":{}}],"aggr_type":1,"allow_download":false,"article_sub_type":0,"article_type":0,"article_url":"http://toutiao.com/group/6561954781336699400/","ban_comment":0,"ban_danmaku":false,"behot_time":1528701219,"bury_count":2250,"cell_flag":262155,"cell_layout_style":1,"cell_type":0,"comment_count":82,"content_decoration":"","cursor":1528701219999,"danmaku_count":0,"digg_count":1805,"display_url":"http://toutiao.com/group/6561954781336699400/","filter_words":[{"id":"8:0","is_selected":false,"name":"看过了"},{"id":"9:1","is_selected":false,"name":"内容太水"},{"id":"5:2074939231","is_selected":false,"name":"拉黑作者:小军生活圈"},{"id":"6:16087","is_selected":false,"name":"不想看:美女"}],"forward_info":{"forward_count":7},"group_flags":32832,"group_id":6561954781336699400,"has_m3u8_video":false,"has_mp4_video":0,"has_video":true,"hot":0,"ignore_web_transform":1,"interaction_data":"","is_subject":false,"item_id":6561954781336699400,"item_version":0,"keywords":"视频,美女","large_image_list":[{"height":326,"uri":"video1609/896f00091aee6992e724","url":"http://p1.pstatp.com/video1609/896f00091aee6992e724","url_list":[{"url":"http://p1.pstatp.com/video1609/896f00091aee6992e724"},{"url":"http://pb3.pstatp.com/video1609/896f00091aee6992e724"},{"url":"http://pb9.pstatp.com/video1609/896f00091aee6992e724"}],"width":580}],"level":0,"log_pb":{"impr_id":"20180611151339010008061137517240"},"media_info":{"avatar_url":"http://p9.pstatp.com/large/46f800012fe8ec43d9d9","follow":false,"is_star_user":false,"media_id":1584581706610701,"name":"小军生活圈","recommend_reason":"","recommend_type":0,"user_id":68698278295,"user_verified":true,"verified_content":""},"media_name":"小军生活圈","middle_image":{"height":360,"uri":"list/896f00091aee6992e724","url":"http://p1.pstatp.com/list/300x196/896f00091aee6992e724.webp","url_list":[{"url":"http://p1.pstatp.com/list/300x196/896f00091aee6992e724.webp"},{"url":"http://pb3.pstatp.com/list/300x196/896f00091aee6992e724.webp"},{"url":"http://pb9.pstatp.com/list/300x196/896f00091aee6992e724.webp"}],"width":640},"need_client_impr_recycle":1,"publish_time":1527904800,"read_count":566989,"repin_count":179,"rid":"20180611151339010008061137517240","share_count":6043,"share_info":{"cover_image":null,"description":null,"share_type":{"pyq":2,"qq":0,"qzone":0,"wx":0},"share_url":"http://m.toutiaoimg.cn/a6561954781336699400/?iid=0\u0026app=news_article","title":"美女相亲玩套路,看小伙如何整治美女?","token_type":1,"weixin_cover_image":{"height":1034,"uri":"large/pgc-image/15281038563343f512fdb88","url":"http://p3.pstatp.com/large/pgc-image/15281038563343f512fdb88","url_list":[{"url":"http://p3.pstatp.com/large/pgc-image/15281038563343f512fdb88"},{"url":"http://pb9.pstatp.com/large/pgc-image/15281038563343f512fdb88"},{"url":"http://pb1.pstatp.com/large/pgc-image/15281038563343f512fdb88"}],"width":1280}},"share_type":2,"share_url":"http://m.toutiaoimg.cn/a6561954781336699400/?iid=0\u0026app=news_article","show_dislike":true,"show_portrait":false,"show_portrait_article":false,"source":"小军生活圈","source_icon_style":1,"source_open_url":"sslocal://profile?refer=video\u0026uid=68698278295","tag":"video_movie","tag_id":6561954781336699400,"tip":0,"title":"美女相亲玩套路,看小伙如何整治美女?","ugc_recommend":{"activity":"","reason":"头条视频原创作者"},"url":"http://toutiao.com/group/6561954781336699400/","user_info":{"avatar_url":"http://p3.pstatp.com/thumb/46f800012fe8ec43d9d9","description":"每天推送原创搞笑视频,高端黑","follow":false,"follower_count":0,"name":"小军生活圈","user_auth_info":"{\"auth_type\": \"0\", \"other_auth\": {\"pgc\": \"头条视频原创作者\"}, \"auth_info\": \"头条视频原创作者\"}","user_id":68698278295,"user_verified":true,"verified_content":"头条视频原创作者"},"user_repin":0,"user_verified":1,"verified_content":"头条视频原创作者","video_detail_info":{"detail_video_large_image":{"height":326,"uri":"video1609/896f00091aee6992e724","url":"http://p1.pstatp.com/video1609/896f00091aee6992e724","url_list":[{"url":"http://p1.pstatp.com/video1609/896f00091aee6992e724"},{"url":"http://pb3.pstatp.com/video1609/896f00091aee6992e724"},{"url":"http://pb9.pstatp.com/video1609/896f00091aee6992e724"}],"width":580},"direct_play":1,"group_flags":32832,"show_pgc_subscribe":1,"video_id":"v02004c20000bc8bkmdqg5b4ln25l570","video_preloading_flag":1,"video_type":0,"video_watch_count":1499723,"video_watching_count":0},"video_duration":126,"video_id":"v02004c20000bc8bkmdqg5b4ln25l570","video_style":3
}

1.3 Retrofit

打开RetrofitService.java
增加两个需要用到的网络请求注解:

RetrofitService.java

然后到RetrofitHelper.java中写上Get方法

RetrofitHelper.java

到Api.java类中添加头条的host

Api.java

2.1 Model层

Model层的内容:


Model

状态监听接口IVideoLoadListener:

IVideoLoadListener

VideoModel的接口IVideoModel:

IVideoModel.java

发送请求的VideoModel类:

VideoModel.java

这里的视频播放列表地址的获取经历以下过程:
发送今日头条请求(getToday)获取到content数组 ==》 把content数组里的字符串用Gson转换为Json数据 ==》把json数据里的video_id取出来,做一系列加密和拼接操作得到获取视频播放地址的api ==》发送获取视频播放地址的网络请求(getVideoUrl) ==》取出单个videoUrlBean,把他们放进对象数组里。

如果用正常的网络获取的话,需要写一个嵌套请求,里面还得有一个循环。
用RxJava写的话,具体的操作就是各种flatMap的转换就行了。

2.2 View层

View层的内容只有一个IVideoView接口:

image.png

2.3 Presenter层

Presenter层的内容:


Presenter

IVideoPresenter接口:


IVideoPresenter

VideoPresenter:

public class VideoPresenter implements IVideoPresenter, IVideoLoadListener {

private IVideoModel iVideoModel;
private IVideoView iVideoView;

public VideoPresenter(IVideoView iVideoView) {
    this.iVideoView = iVideoView;
    this.iVideoModel = new VideoModel();
}

@Override
public void loadVideo() {
    iVideoView.showDialog();
    iVideoModel.loadVideo("video", this);
}

@Override
public void videoUrlSuccess(List<VideoUrlBean> mainUrlBeans, List<TodayContentBean> contentBeans) {
    List<String> videoList = new ArrayList<>();
    iVideoView.hideDialog();
    for (int i = 0; i < mainUrlBeans.size(); i++) {
        String mainUrl = mainUrlBeans.get(i).getData().getVideo_list().getVideo_1().getMain_url();
        final String url1 = (new String(Base64.decode(mainUrl.getBytes(), Base64.DEFAULT)));
        videoList.add(url1);
    }
    iVideoView.showVideo(contentBeans, videoList);
}

@Override
public void fail(Throwable throwable) {
    iVideoView.hideDialog();
    iVideoView.showErrorMsg(throwable);
}

public static String getVideoContentApi(String videoid) {
    String VIDEO_HOST = "http://ib.365yg.com";
    String VIDEO_URL = "/video/urls/v/1/toutiao/mp4/%s?r=%s";
    String r = getRandom();
    String s = String.format(VIDEO_URL, videoid, r);
    CRC32 crc32 = new CRC32();
    crc32.update(s.getBytes());
    String crcString = crc32.getValue() + "";
    String url = VIDEO_HOST + s + "&s=" + crcString;
    return url;
}

public static String getRandom() {
    Random random = new Random();
    StringBuilder result = new StringBuilder();
    for (int i = 0; i < 16; i++) {
        result.append(random.nextInt(10));
    }
    return result.toString();
}

public static TodayContentBean getTodayContentBean(String content) {
    Gson gson = new Gson();
    TodayContentBean bean = gson.fromJson(content, TodayContentBean.class);
    return bean;
}
}

3.1 item_video

由于我们的视频需要列表显示,所以还是得用到RecyclerView
而每一个item我们用的是基于MediaPlayerIJKplayer,和ExoPlayer的第三方视频库JiaoZiVideoPlayer

首先新建一个布局文件,命名为item_video

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_margin="4dp"
app:cardCornerRadius="5dp">

<cn.jzvd.JZVideoPlayerStandard
    android:id="@+id/video_player"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorWhite"></cn.jzvd.JZVideoPlayerStandard>
</android.support.v7.widget.CardView>

再到fg_video.xml文件中,把RecyclerView加上去:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<android.support.v4.widget.SwipeRefreshLayout
    android:id="@+id/srl_video"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_video"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    </android.support.v7.widget.RecyclerView>
</android.support.v4.widget.SwipeRefreshLayout>

</LinearLayout>

3.2 ItemVideoAdapter

为我们刚刚写的item写一个适配器:

新建一个java文件,命名为ItemVideoAdapter

ItemVideoAdapter

3.3 Fragment

前面做了那么多,最终还是要在Fragment上设置才能让他们显示出来

FgVideoFragment.java
public class FgVideoFragment extends Fragment  implements IVideoView{

private IVideoPresenter iVideoPresenter;
private RecyclerView rv_video;
private ItemVideoAdapter itemVideoAdapter;
private SwipeRefreshLayout srl_video;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    return inflater.inflate(R.layout.fg_video,container,false);
}

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    iVideoPresenter = new VideoPresenter(this);
    rv_video = view.findViewById(R.id.rv_video);
    srl_video = view.findViewById(R.id.srl_video);
    srl_video.setColorSchemeColors(Color.parseColor("#ffce3d3a"));
    iVideoPresenter.loadVideo();
    srl_video.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            iVideoPresenter.loadVideo();
        }
    });
    itemVideoAdapter = new ItemVideoAdapter(getActivity());
}

@Override
public void showVideo(List<TodayContentBean> todayContentBeans, List<String> videoList) {
    itemVideoAdapter.setData(todayContentBeans, videoList);
    rv_video.setLayoutManager(new LinearLayoutManager(getActivity(),
            LinearLayoutManager.VERTICAL, false));
    rv_video.setAdapter(itemVideoAdapter);
}

@Override
public void hideDialog() {
    srl_video.setRefreshing(false);
}

@Override
public void showDialog() {
    srl_video.setRefreshing(true);
}

@Override
public void showErrorMsg(Throwable throwable) {
    Toast.makeText(getContext(), "加载出错:"+throwable.getMessage(), Toast.LENGTH_SHORT).show();
}
}

最终效果:

视频模块效果2.gif

学习任务

根据提供的城市代码数组,通过以下Api,写一个RxJava的网络请求,把相应的城市情况Log出来。
数组:

Integer[] city={101280101,101280102,101280103,101280104,101280105, 101280201,101280202,101280203,101280204,101280205,101280206, 101280207,101280208,101280501};

api:

http://wthrcdn.etouch.cn/weather_mini?citykey=101010100

效果:


log

项目源码:https://github.com/Huigesi/IdleReaderDemo

上一章:
第四章 电影模块

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,448评论 25 707
  • 前言: 正在做一个资讯类app,打算一边做一边整理,供自己学习与巩固。用到的知识复杂度不高,仅适于新手。经验不多,...
    Huigesi阅读 2,320评论 0 5
  • 我们彼此试探,彼此进退,彼此打太极。其实我早知道他的想法。我只是太孤独,害怕失去。他正直,聪明,成熟,理智...
    狼给的温暖阅读 273评论 0 0
  • 宿友常常说我提前进入了老年人的生活,因为我每晚泡脚,每天早睡早起,每天要运动,还把零食和一些垃圾食品戒掉了……之类...
    等等安东尼阅读 5,976评论 1 2
  • 第二章:想了解 作者:董潇潇 “打啊,来,虐你们一局”。 陆诚:“口气不小嘛,来,接球”,说话间把球丢了过...
    董潇潇xiao阅读 1,958评论 9 8