前言:
正在做一个资讯类app,打算一边做一边整理,供自己学习与巩固。用到的知识复杂度不高,仅适于新手。经验不多,如果写出来的代码有不好的地方欢迎讨论。
以往的内容
第五章 视频模块
本章内容最终效果:
知识点:
MVP,RxJava,RecyclerView,JZVideoPlayerStandard
学习目标:
1、MVP模式的使用
2、使用RxJava处理复杂请求过程。
3、使用RecyclerView显示视频列表数据。
视频模块的视频播放方面主要用了第三方视频库JiaoZiVideoPlayer。除了网络视频播放,本章还运用RxJava解决复杂的网络请求(嵌套请求和循环请求)。
项目实战:
注意
本章用到的drawable资源、values资源皆存放在百度网盘
(请将values文件夹中的style.xml或color.xml更新一致后再运行,如有后续更新自行修改)
1.1 项目结构
需导入的库:
导入JiaoZiVideoPlayer。
compile 'cn.jzvd:jiaozivideoplayer:6.2.10'
用到的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
增加两个需要用到的网络请求注解:
然后到RetrofitHelper.java中写上Get方法
到Api.java类中添加头条的host
2.1 Model层
Model层的内容:
状态监听接口IVideoLoadListener:
VideoModel的接口IVideoModel:
发送请求的VideoModel类:
这里的视频播放列表地址的获取经历以下过程:
发送今日头条请求(getToday)获取到content数组 ==》 把content数组里的字符串用Gson转换为Json数据 ==》把json数据里的video_id取出来,做一系列加密和拼接操作得到获取视频播放地址的api ==》发送获取视频播放地址的网络请求(getVideoUrl) ==》取出单个videoUrlBean,把他们放进对象数组里。
如果用正常的网络获取的话,需要写一个嵌套请求,里面还得有一个循环。
用RxJava写的话,具体的操作就是各种flatMap的转换就行了。
2.2 View层
View层的内容只有一个IVideoView接口:
2.3 Presenter层
Presenter层的内容:
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我们用的是基于MediaPlayer,IJKplayer,和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
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();
}
}
最终效果:
学习任务
根据提供的城市代码数组,通过以下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
效果:
项目源码:https://github.com/Huigesi/IdleReaderDemo
上一章:
第四章 电影模块